From 57e0d8f2930690de69c690c41e30abaa2cdb3776 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Fri, 26 Mar 2021 08:20:38 -0400 Subject: Use TMPDIR when commiting images Fixes: https://github.com/containers/podman/issues/9825 Currently we are using TMPDIR for storaing temporary files when building images, but not when you directly commit the images. This change simply uses the TMPDIR environment variable if set to store temporary files. Signed-off-by: Daniel J Walsh --- libpod/image/docker_registry_options.go | 1 + libpod/image/image_test.go | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) (limited to 'libpod') diff --git a/libpod/image/docker_registry_options.go b/libpod/image/docker_registry_options.go index 0a2a375ae..d95234e3d 100644 --- a/libpod/image/docker_registry_options.go +++ b/libpod/image/docker_registry_options.go @@ -69,6 +69,7 @@ func GetSystemContext(signaturePolicyPath, authFilePath string, forceCompress bo sc.AuthFilePath = authFilePath sc.DirForceCompress = forceCompress sc.DockerRegistryUserAgent = fmt.Sprintf("libpod/%s", podmanVersion.Version) + sc.BigFilesTemporaryDir = parse.GetTempDir() return sc } diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index 3e6e7b9db..e2518b687 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -9,6 +9,7 @@ import ( "github.com/containers/podman/v3/libpod/events" "github.com/containers/podman/v3/pkg/util" + podmanVersion "github.com/containers/podman/v3/version" "github.com/containers/storage" "github.com/containers/storage/pkg/reexec" "github.com/opencontainers/go-digest" @@ -298,3 +299,25 @@ func TestNormalizedTag(t *testing.T) { } } } + +func TestGetSystemContext(t *testing.T) { + sc := GetSystemContext("", "", false) + assert.Equal(t, sc.SignaturePolicyPath, "") + assert.Equal(t, sc.AuthFilePath, "") + assert.Equal(t, sc.DirForceCompress, false) + assert.Equal(t, sc.DockerRegistryUserAgent, fmt.Sprintf("libpod/%s", podmanVersion.Version)) + assert.Equal(t, sc.BigFilesTemporaryDir, "/var/tmp") + + oldtmpdir := os.Getenv("TMPDIR") + os.Setenv("TMPDIR", "/mnt") + sc = GetSystemContext("/tmp/foo", "/tmp/bar", true) + assert.Equal(t, sc.SignaturePolicyPath, "/tmp/foo") + assert.Equal(t, sc.AuthFilePath, "/tmp/bar") + assert.Equal(t, sc.DirForceCompress, true) + assert.Equal(t, sc.BigFilesTemporaryDir, "/mnt") + if oldtmpdir != "" { + os.Setenv("TMPDIR", oldtmpdir) + } else { + os.Unsetenv("TMPDIR") + } +} -- cgit v1.2.3-54-g00ecf From 66b1c2bd998e0b0118e56a4dea7880a4eeada998 Mon Sep 17 00:00:00 2001 From: なつき Date: Fri, 26 Mar 2021 13:25:58 -0700 Subject: [NO TESTS NEEDED] Fix for kernel without CONFIG_USER_NS Signed-off-by: Natsuki --- libpod/container_internal_linux.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'libpod') diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 24319f4b5..0fd29e456 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -607,10 +607,16 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { availableUIDs, availableGIDs, err := rootless.GetAvailableIDMaps() if err != nil { - return nil, err + if os.IsNotExist(err) { + // The kernel-provided files only exist if user namespaces are supported + logrus.Debugf("user or group ID mappings not available: %s", err) + } else { + return nil, err + } + } else { + g.Config.Linux.UIDMappings = rootless.MaybeSplitMappings(g.Config.Linux.UIDMappings, availableUIDs) + g.Config.Linux.GIDMappings = rootless.MaybeSplitMappings(g.Config.Linux.GIDMappings, availableGIDs) } - g.Config.Linux.UIDMappings = rootless.MaybeSplitMappings(g.Config.Linux.UIDMappings, availableUIDs) - g.Config.Linux.GIDMappings = rootless.MaybeSplitMappings(g.Config.Linux.GIDMappings, availableGIDs) // Hostname handling: // If we have a UTS namespace, set Hostname in the OCI spec. -- cgit v1.2.3-54-g00ecf From 573ed9220b9b295eacdd017dc87bbb592043cff4 Mon Sep 17 00:00:00 2001 From: Jakub Guzik Date: Thu, 25 Mar 2021 01:32:00 +0100 Subject: Fix filters in image http compat/libpod api endpoints Signed-off-by: Jakub Guzik --- libpod/image/prune.go | 7 +++++ pkg/api/handlers/compat/images_prune.go | 19 ++++--------- pkg/api/handlers/libpod/images.go | 18 ++++++------ pkg/api/handlers/utils/images.go | 14 ++++++---- test/apiv2/10-images.at | 49 +++++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 27 deletions(-) (limited to 'libpod') diff --git a/libpod/image/prune.go b/libpod/image/prune.go index d6ae5feaf..21867698a 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -2,6 +2,7 @@ package image import ( "context" + "strconv" "strings" "time" @@ -52,6 +53,12 @@ func generatePruneFilterFuncs(filter, filterValue string) (ImageFilter, error) { } return false }, nil + case "dangling": + danglingImages, err := strconv.ParseBool(filterValue) + if err != nil { + return nil, errors.Wrapf(err, "invalid filter dangling=%s", filterValue) + } + return ImageFilter(DanglingFilter(danglingImages)), nil } return nil, nil } diff --git a/pkg/api/handlers/compat/images_prune.go b/pkg/api/handlers/compat/images_prune.go index 63daaa780..ddf559ec6 100644 --- a/pkg/api/handlers/compat/images_prune.go +++ b/pkg/api/handlers/compat/images_prune.go @@ -8,8 +8,8 @@ import ( "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" + "github.com/containers/podman/v3/pkg/util" "github.com/docker/docker/api/types" - "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -17,27 +17,20 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { var ( filters []string ) - decoder := r.Context().Value("decoder").(*schema.Decoder) runtime := r.Context().Value("runtime").(*libpod.Runtime) - query := struct { - All bool - Filters map[string][]string `schema:"filters"` - }{ - // This is where you can override the golang default value for one of fields - } - - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + filterMap, err := util.PrepareFilters(r) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - for k, v := range query.Filters { + for k, v := range *filterMap { for _, val := range v { filters = append(filters, fmt.Sprintf("%s=%s", k, val)) } } - imagePruneReports, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, filters) + imagePruneReports, err := runtime.ImageRuntime().PruneImages(r.Context(), false, filters) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 1f306a533..158babcdc 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -22,6 +22,7 @@ import ( "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" "github.com/containers/podman/v3/pkg/errorhandling" + "github.com/containers/podman/v3/pkg/util" utils2 "github.com/containers/podman/v3/utils" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -125,31 +126,32 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { - All bool `schema:"all"` - Filters map[string][]string `schema:"filters"` + All bool `schema:"all"` }{ // override any golang type defaults } - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + filterMap, err := util.PrepareFilters(r) + + if dErr := decoder.Decode(&query, r.URL.Query()); dErr != nil || err != nil { + utils.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } var libpodFilters = []string{} if _, found := r.URL.Query()["filters"]; found { - dangling := query.Filters["all"] + dangling := (*filterMap)["all"] if len(dangling) > 0 { - query.All, err = strconv.ParseBool(query.Filters["all"][0]) + query.All, err = strconv.ParseBool((*filterMap)["all"][0]) if err != nil { utils.InternalServerError(w, err) return } } // dangling is special and not implemented in the libpod side of things - delete(query.Filters, "dangling") - for k, v := range query.Filters { + delete(*filterMap, "dangling") + for k, v := range *filterMap { libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) } } diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index 743629db8..da3c9e985 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -11,6 +11,7 @@ import ( "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/image" + "github.com/containers/podman/v3/pkg/util" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -58,13 +59,17 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { All bool - Filters map[string][]string `schema:"filters"` Digests bool Filter string // Docker 1.24 compatibility }{ // This is where you can override the golang default value for one of fields } + filterMap, err := util.PrepareFilters(r) + if err != nil { + return nil, err + } + if err := decoder.Decode(&query, r.URL.Query()); err != nil { return nil, err } @@ -72,12 +77,9 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { if _, found := r.URL.Query()["digests"]; found && query.Digests { UnSupportedParameter("digests") } - var ( - images []*image.Image - err error - ) + var images []*image.Image - queryFilters := query.Filters + queryFilters := *filterMap if !IsLibpodRequest(r) && len(query.Filter) > 0 { // Docker 1.24 compatibility if queryFilters == nil { queryFilters = make(map[string][]string) diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at index 4ebaeff45..f854d38ab 100644 --- a/test/apiv2/10-images.at +++ b/test/apiv2/10-images.at @@ -77,6 +77,55 @@ for i in $iid ${iid:0:12} $PODMAN_TEST_IMAGE_NAME; do t GET "libpod/images/$i/get?compress=false" 200 '[POSIX tar archive]' done +#compat api list images sanity checks +t GET images/json?filters='garb1age}' 500 \ + .cause="invalid character 'g' looking for beginning of value" +t GET images/json?filters='{"label":["testl' 500 \ + .cause="unexpected end of JSON input" + +#libpod api list images sanity checks +t GET libpod/images/json?filters='garb1age}' 500 \ + .cause="invalid character 'g' looking for beginning of value" +t GET libpod/images/json?filters='{"label":["testl' 500 \ + .cause="unexpected end of JSON input" + +# Prune images - bad filter input +t POST images/prune?filters='garb1age}' 500 \ + .cause="invalid character 'g' looking for beginning of value" +t POST libpod/images/prune?filters='garb1age}' 500 \ + .cause="invalid character 'g' looking for beginning of value" + +## Prune images with illformed label +t POST images/prune?filters='{"label":["tes' 500 \ + .cause="unexpected end of JSON input" +t POST libpod/images/prune?filters='{"label":["tes' 500 \ + .cause="unexpected end of JSON input" + + +#create, list and remove dangling image +podman image build -t test:test -<file1 +EOF + +podman image build -t test:test --label xyz -<file2 +EOF + +t GET images/json?filters='{"dangling":["true"]}' 200 length=1 +t POST images/prune?filters='{"dangling":["true"]}' 200 +t GET images/json?filters='{"dangling":["true"]}' 200 length=0 + +#label filter check in libpod and compat +t GET images/json?filters='{"label":["xyz"]}' 200 length=1 +t GET libpod/images/json?filters='{"label":["xyz"]}' 200 length=1 + +t DELETE libpod/images/test:test 200 + +t GET images/json?filters='{"label":["xyz"]}' 200 length=0 +t GET libpod/images/json?filters='{"label":["xyz"]}' 200 length=0 + # Export more than one image # FIXME FIXME FIXME, this doesn't work: # not ok 64 [10-images] GET images/get?names=alpine,busybox : status -- cgit v1.2.3-54-g00ecf From 5e3445e6e094b2e993962cd37ff0b5b50564bf61 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Fri, 19 Mar 2021 14:55:08 -0400 Subject: Ensure manually-created volumes have correct ownership As part of a fix for an earlier bug (#5698) we added the ability for Podman to chown volumes to correctly match the user running in the container, even in adverse circumstances (where we don't know the right UID/GID until very late in the process). However, we only did this for volumes created automatically by a `podman run` or `podman create`. Volumes made by `podman volume create` do not get this chown, so their permissions may not be correct. I've looked, and I don't think there's a good reason not to do this chwon for all volumes the first time the container is started. I would prefer to do this as part of volume copy-up, but I don't think that's really possible (copy-up happens earlier in the process and we don't have a spec). There is a small chance, as things stand, that a copy-up happens for one container and then a chown for a second, unrelated container, but the odds of this are astronomically small (we'd need a very close race between two starting containers). Fixes #9608 Signed-off-by: Matthew Heon --- libpod/options.go | 13 ------------- libpod/runtime_ctr.go | 2 +- libpod/volume_internal.go | 1 + test/e2e/run_volume_test.go | 26 ++++++++++++++++++++++++++ 4 files changed, 28 insertions(+), 14 deletions(-) (limited to 'libpod') diff --git a/libpod/options.go b/libpod/options.go index 85862cc17..ffdb5abbf 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1627,19 +1627,6 @@ func WithVolumeGID(gid int) VolumeCreateOption { } } -// WithVolumeNeedsChown sets the NeedsChown flag for the volume. -func WithVolumeNeedsChown() VolumeCreateOption { - return func(volume *Volume) error { - if volume.valid { - return define.ErrVolumeFinalized - } - - volume.state.NeedsChown = true - - return nil - } -} - // withSetAnon sets a bool notifying libpod that this volume is anonymous and // should be removed when containers using it are removed and volumes are // specified for removal. diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 19690d79b..537618b65 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -392,7 +392,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai logrus.Debugf("Creating new volume %s for container", vol.Name) // The volume does not exist, so we need to create it. - volOptions := []VolumeCreateOption{WithVolumeName(vol.Name), WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID()), WithVolumeNeedsChown()} + volOptions := []VolumeCreateOption{WithVolumeName(vol.Name), WithVolumeUID(ctr.RootUID()), WithVolumeGID(ctr.RootGID())} if isAnonymous { volOptions = append(volOptions, withSetAnon()) } diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go index c1dbe00fd..694cdd149 100644 --- a/libpod/volume_internal.go +++ b/libpod/volume_internal.go @@ -17,6 +17,7 @@ func newVolume(runtime *Runtime) *Volume { volume.config.Labels = make(map[string]string) volume.config.Options = make(map[string]string) volume.state.NeedsCopyUp = true + volume.state.NeedsChown = true return volume } diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index a0407804f..cba64cc37 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -642,4 +642,30 @@ VOLUME /test/`, ALPINE) found, _ = session.GrepString("888:888") Expect(found).Should(BeTrue()) }) + + It("volume permissions after run", func() { + imgName := "testimg" + dockerfile := `FROM fedora-minimal +RUN useradd -m testuser -u 1005 +USER testuser` + podmanTest.BuildImage(dockerfile, imgName, "false") + + testString := "testuser testuser" + + test1 := podmanTest.Podman([]string{"run", "-v", "testvol1:/test", imgName, "bash", "-c", "ls -al /test | grep -v root | grep -v total"}) + test1.WaitWithDefaultTimeout() + Expect(test1.ExitCode()).To(Equal(0)) + Expect(strings.Contains(test1.OutputToString(), testString)).To(BeTrue()) + + volName := "testvol2" + vol := podmanTest.Podman([]string{"volume", "create", volName}) + vol.WaitWithDefaultTimeout() + Expect(vol.ExitCode()).To(Equal(0)) + + test2 := podmanTest.Podman([]string{"run", "-v", fmt.Sprintf("%s:/test", volName), imgName, "bash", "-c", "ls -al /test | grep -v root | grep -v total"}) + test2.WaitWithDefaultTimeout() + Expect(test2.ExitCode()).To(Equal(0)) + Expect(strings.Contains(test2.OutputToString(), testString)).To(BeTrue()) + + }) }) -- cgit v1.2.3-54-g00ecf From 23f3805df253a6144a7d6891628b6da533a8ecc0 Mon Sep 17 00:00:00 2001 From: Phoenix The Fallen Date: Wed, 24 Mar 2021 22:53:53 +0300 Subject: [NO TESTS NEEDED] Fix rootless volume plugins In a case of volume plugins with custom options. Signed-off-by: Phoenix The Fallen --- libpod/options.go | 9 --------- libpod/volume_internal_linux.go | 10 ++++++---- 2 files changed, 6 insertions(+), 13 deletions(-) (limited to 'libpod') diff --git a/libpod/options.go b/libpod/options.go index ffdb5abbf..24e9d74f4 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1577,8 +1577,6 @@ func WithVolumeLabels(labels map[string]string) VolumeCreateOption { } // WithVolumeOptions sets the options of the volume. -// If the "local" driver has been selected, options will be validated. There are -// currently 3 valid options for the "local" driver - o, type, and device. func WithVolumeOptions(options map[string]string) VolumeCreateOption { return func(volume *Volume) error { if volume.valid { @@ -1587,13 +1585,6 @@ func WithVolumeOptions(options map[string]string) VolumeCreateOption { volume.config.Options = make(map[string]string) for key, value := range options { - switch key { - case "type", "device", "o", "UID", "GID": - volume.config.Options[key] = value - default: - return errors.Wrapf(define.ErrInvalidArg, "unrecognized volume option %q is not supported with local driver", key) - } - volume.config.Options[key] = value } diff --git a/libpod/volume_internal_linux.go b/libpod/volume_internal_linux.go index 67ac41874..92391de1d 100644 --- a/libpod/volume_internal_linux.go +++ b/libpod/volume_internal_linux.go @@ -32,8 +32,10 @@ func (v *Volume) mount() error { return nil } - // We cannot mount volumes as rootless. - if rootless.IsRootless() { + // We cannot mount 'local' volumes as rootless. + if !v.UsesVolumeDriver() && rootless.IsRootless() { + // This check should only be applied to 'local' driver + // so Volume Drivers must be excluded return errors.Wrapf(define.ErrRootless, "cannot mount volumes without root privileges") } @@ -137,8 +139,8 @@ func (v *Volume) unmount(force bool) error { return nil } - // We cannot unmount volumes as rootless. - if rootless.IsRootless() { + // We cannot unmount 'local' volumes as rootless. + if !v.UsesVolumeDriver() && rootless.IsRootless() { // If force is set, just clear the counter and bail without // error, so we can remove volumes from the state if they are in // an awkward configuration. -- cgit v1.2.3-54-g00ecf From 183a68a817208821a9fc79a3c9c19202a63dfb44 Mon Sep 17 00:00:00 2001 From: Jakub Guzik Date: Tue, 23 Mar 2021 00:13:44 +0100 Subject: Unification of label filter across list/prune endpoints Signed-off-by: Jakub Guzik --- libpod/image/filters.go | 14 ++------ libpod/image/prune.go | 15 ++------ libpod/network/netconflist.go | 27 ++------------ pkg/domain/filters/containers.go | 22 +----------- pkg/domain/filters/pods.go | 21 +---------- pkg/domain/filters/volumes.go | 17 ++------- pkg/util/filters.go | 23 +++++++++++- pkg/util/filters_test.go | 77 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 111 insertions(+), 105 deletions(-) create mode 100644 pkg/util/filters_test.go (limited to 'libpod') diff --git a/libpod/image/filters.go b/libpod/image/filters.go index 37d3cb6a5..d316c6956 100644 --- a/libpod/image/filters.go +++ b/libpod/image/filters.go @@ -9,6 +9,7 @@ import ( "time" "github.com/containers/podman/v3/pkg/inspect" + "github.com/containers/podman/v3/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -78,23 +79,14 @@ func ReadOnlyFilter(readOnly bool) ResultFilter { } // LabelFilter allows you to filter by images labels key and/or value -func LabelFilter(ctx context.Context, labelfilter string) ResultFilter { +func LabelFilter(ctx context.Context, filter string) ResultFilter { // We need to handle both label=key and label=key=value return func(i *Image) bool { - var value string - splitFilter := strings.SplitN(labelfilter, "=", 2) - key := splitFilter[0] - if len(splitFilter) > 1 { - value = splitFilter[1] - } labels, err := i.Labels(ctx) if err != nil { return false } - if len(strings.TrimSpace(labels[key])) > 0 && len(strings.TrimSpace(value)) == 0 { - return true - } - return labels[key] == value + return util.MatchLabelFilters([]string{filter}, labels) } } diff --git a/libpod/image/prune.go b/libpod/image/prune.go index 21867698a..1ef35ef83 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -9,6 +9,7 @@ import ( "github.com/containers/podman/v3/libpod/events" "github.com/containers/podman/v3/pkg/domain/entities/reports" "github.com/containers/podman/v3/pkg/timetype" + "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -17,24 +18,12 @@ import ( func generatePruneFilterFuncs(filter, filterValue string) (ImageFilter, error) { switch filter { case "label": - var filterArray = strings.SplitN(filterValue, "=", 2) - var filterKey = filterArray[0] - if len(filterArray) > 1 { - filterValue = filterArray[1] - } else { - filterValue = "" - } return func(i *Image) bool { labels, err := i.Labels(context.Background()) if err != nil { return false } - for labelKey, labelValue := range labels { - if labelKey == filterKey && (filterValue == "" || labelValue == filterValue) { - return true - } - } - return false + return util.MatchLabelFilters([]string{filterValue}, labels) }, nil case "until": diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go index b358bc530..179620a0d 100644 --- a/libpod/network/netconflist.go +++ b/libpod/network/netconflist.go @@ -225,7 +225,7 @@ func IfPassesFilter(netconf *libcni.NetworkConfigList, filters map[string][]stri case "label": // matches all labels - result = matchPruneLabelFilters(netconf, filterValues) + result = util.MatchLabelFilters(filterValues, GetNetworkLabels(netconf)) case "driver": // matches only for the DefaultNetworkDriver @@ -260,7 +260,7 @@ func IfPassesPruneFilter(config *config.Config, netconf *libcni.NetworkConfigLis for key, filterValues := range f { switch strings.ToLower(key) { case "label": - return matchPruneLabelFilters(netconf, filterValues), nil + return util.MatchLabelFilters(filterValues, GetNetworkLabels(netconf)), nil case "until": until, err := util.ComputeUntilTimestamp(key, filterValues) if err != nil { @@ -280,29 +280,6 @@ func IfPassesPruneFilter(config *config.Config, netconf *libcni.NetworkConfigLis return false, nil } -func matchPruneLabelFilters(netconf *libcni.NetworkConfigList, filterValues []string) bool { - labels := GetNetworkLabels(netconf) - result := true -outer: - for _, filterValue := range filterValues { - filterArray := strings.SplitN(filterValue, "=", 2) - filterKey := filterArray[0] - if len(filterArray) > 1 { - filterValue = filterArray[1] - } else { - filterValue = "" - } - for labelKey, labelValue := range labels { - if labelKey == filterKey && (filterValue == "" || labelValue == filterValue) { - result = true - continue outer - } - } - result = false - } - return result -} - func getCreatedTimestamp(config *config.Config, netconf *libcni.NetworkConfigList) (*time.Time, error) { networkConfigPath, err := GetCNIConfigPathByNameOrID(config, netconf.Name) if err != nil { diff --git a/pkg/domain/filters/containers.go b/pkg/domain/filters/containers.go index 02727e841..9cae01dc0 100644 --- a/pkg/domain/filters/containers.go +++ b/pkg/domain/filters/containers.go @@ -23,27 +23,7 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo case "label": // we have to match that all given labels exits on that container return func(c *libpod.Container) bool { - labels := c.Labels() - for _, filterValue := range filterValues { - matched := false - filterArray := strings.SplitN(filterValue, "=", 2) - filterKey := filterArray[0] - if len(filterArray) > 1 { - filterValue = filterArray[1] - } else { - filterValue = "" - } - for labelKey, labelValue := range labels { - if labelKey == filterKey && (filterValue == "" || labelValue == filterValue) { - matched = true - break - } - } - if !matched { - return false - } - } - return true + return util.MatchLabelFilters(filterValues, c.Labels()) }, nil case "name": // we only have to match one name diff --git a/pkg/domain/filters/pods.go b/pkg/domain/filters/pods.go index 0490a4848..9a1c7d19d 100644 --- a/pkg/domain/filters/pods.go +++ b/pkg/domain/filters/pods.go @@ -114,26 +114,7 @@ func GeneratePodFilterFunc(filter string, filterValues []string) ( case "label": return func(p *libpod.Pod) bool { labels := p.Labels() - for _, filterValue := range filterValues { - matched := false - filterArray := strings.SplitN(filterValue, "=", 2) - filterKey := filterArray[0] - if len(filterArray) > 1 { - filterValue = filterArray[1] - } else { - filterValue = "" - } - for labelKey, labelValue := range labels { - if labelKey == filterKey && (filterValue == "" || labelValue == filterValue) { - matched = true - break - } - } - if !matched { - return false - } - } - return true + return util.MatchLabelFilters(filterValues, labels) }, nil case "network": return func(p *libpod.Pod) bool { diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go index bc1756cf5..9b2fc5280 100644 --- a/pkg/domain/filters/volumes.go +++ b/pkg/domain/filters/volumes.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/containers/podman/v3/libpod" + "github.com/containers/podman/v3/pkg/util" "github.com/pkg/errors" ) @@ -29,21 +30,9 @@ func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) { return v.Scope() == scopeVal }) case "label": - filterArray := strings.SplitN(val, "=", 2) - filterKey := filterArray[0] - var filterVal string - if len(filterArray) > 1 { - filterVal = filterArray[1] - } else { - filterVal = "" - } + filter := val vf = append(vf, func(v *libpod.Volume) bool { - for labelKey, labelValue := range v.Labels() { - if labelKey == filterKey && (filterVal == "" || labelValue == filterVal) { - return true - } - } - return false + return util.MatchLabelFilters([]string{filter}, v.Labels()) }) case "opt": filterArray := strings.SplitN(val, "=", 2) diff --git a/pkg/util/filters.go b/pkg/util/filters.go index 51b2c5331..90daef402 100644 --- a/pkg/util/filters.go +++ b/pkg/util/filters.go @@ -11,7 +11,7 @@ import ( "github.com/pkg/errors" ) -// ComputeUntilTimestamp extracts unitil timestamp from filters +// ComputeUntilTimestamp extracts until timestamp from filters func ComputeUntilTimestamp(filter string, filterValues []string) (time.Time, error) { invalid := time.Time{} if len(filterValues) != 1 { @@ -93,3 +93,24 @@ func PrepareFilters(r *http.Request) (*map[string][]string, error) { } return &filterMap, nil } + +// MatchLabelFilters matches labels and returs true if they are valid +func MatchLabelFilters(filterValues []string, labels map[string]string) bool { +outer: + for _, filterValue := range filterValues { + filterArray := strings.SplitN(filterValue, "=", 2) + filterKey := filterArray[0] + if len(filterArray) > 1 { + filterValue = filterArray[1] + } else { + filterValue = "" + } + for labelKey, labelValue := range labels { + if labelKey == filterKey && (filterValue == "" || labelValue == filterValue) { + continue outer + } + } + return false + } + return true +} diff --git a/pkg/util/filters_test.go b/pkg/util/filters_test.go new file mode 100644 index 000000000..4771f682b --- /dev/null +++ b/pkg/util/filters_test.go @@ -0,0 +1,77 @@ +package util + +import "testing" + +func TestMatchLabelFilters(t *testing.T) { + testLabels := map[string]string{ + "label1": "", + "label2": "test", + "label3": "", + } + type args struct { + filterValues []string + labels map[string]string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Match when all filters the same as labels", + args: args{ + filterValues: []string{"label1", "label3", "label2=test"}, + labels: testLabels, + }, + want: true, + }, + { + name: "Match when filter value not provided in args", + args: args{ + filterValues: []string{"label2"}, + labels: testLabels, + }, + want: true, + }, + { + name: "Match when no filter value is given", + args: args{ + filterValues: []string{"label2="}, + labels: testLabels, + }, + want: true, + }, + { + name: "Do not match when filter value differs", + args: args{ + filterValues: []string{"label2=differs"}, + labels: testLabels, + }, + want: false, + }, + { + name: "Do not match when filter value not listed in labels", + args: args{ + filterValues: []string{"label1=xyz"}, + labels: testLabels, + }, + want: false, + }, + { + name: "Do not match when one from many not ok", + args: args{ + filterValues: []string{"label1=xyz", "invalid=valid"}, + labels: testLabels, + }, + want: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + if got := MatchLabelFilters(tt.args.filterValues, tt.args.labels); got != tt.want { + t.Errorf("MatchLabelFilters() = %v, want %v", got, tt.want) + } + }) + } +} -- cgit v1.2.3-54-g00ecf From a01b387e8251bef5fc73f3e8ac72cc5db9ecea9e Mon Sep 17 00:00:00 2001 From: Jakub Guzik Date: Wed, 24 Mar 2021 00:42:42 +0100 Subject: Unification of until filter across list/prune endpoints Signed-off-by: Jakub Guzik --- libpod/image/prune.go | 9 +-------- libpod/network/netconflist.go | 2 +- pkg/domain/filters/containers.go | 2 +- pkg/util/filters.go | 4 ++-- pkg/util/filters_test.go | 38 +++++++++++++++++++++++++++++++++++++- 5 files changed, 42 insertions(+), 13 deletions(-) (limited to 'libpod') diff --git a/libpod/image/prune.go b/libpod/image/prune.go index 1ef35ef83..12727901a 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -4,11 +4,9 @@ import ( "context" "strconv" "strings" - "time" "github.com/containers/podman/v3/libpod/events" "github.com/containers/podman/v3/pkg/domain/entities/reports" - "github.com/containers/podman/v3/pkg/timetype" "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage" "github.com/pkg/errors" @@ -27,15 +25,10 @@ func generatePruneFilterFuncs(filter, filterValue string) (ImageFilter, error) { }, nil case "until": - ts, err := timetype.GetTimestamp(filterValue, time.Now()) + until, err := util.ComputeUntilTimestamp([]string{filterValue}) if err != nil { return nil, err } - seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0) - if err != nil { - return nil, err - } - until := time.Unix(seconds, nanoseconds) return func(i *Image) bool { if !until.IsZero() && i.Created().After((until)) { return true diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go index 179620a0d..08816f2bd 100644 --- a/libpod/network/netconflist.go +++ b/libpod/network/netconflist.go @@ -262,7 +262,7 @@ func IfPassesPruneFilter(config *config.Config, netconf *libcni.NetworkConfigLis case "label": return util.MatchLabelFilters(filterValues, GetNetworkLabels(netconf)), nil case "until": - until, err := util.ComputeUntilTimestamp(key, filterValues) + until, err := util.ComputeUntilTimestamp(filterValues) if err != nil { return false, err } diff --git a/pkg/domain/filters/containers.go b/pkg/domain/filters/containers.go index 9cae01dc0..84cf03764 100644 --- a/pkg/domain/filters/containers.go +++ b/pkg/domain/filters/containers.go @@ -165,7 +165,7 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo return false }, nil case "until": - until, err := util.ComputeUntilTimestamp(filter, filterValues) + until, err := util.ComputeUntilTimestamp(filterValues) if err != nil { return nil, err } diff --git a/pkg/util/filters.go b/pkg/util/filters.go index 90daef402..43bf646f1 100644 --- a/pkg/util/filters.go +++ b/pkg/util/filters.go @@ -12,10 +12,10 @@ import ( ) // ComputeUntilTimestamp extracts until timestamp from filters -func ComputeUntilTimestamp(filter string, filterValues []string) (time.Time, error) { +func ComputeUntilTimestamp(filterValues []string) (time.Time, error) { invalid := time.Time{} if len(filterValues) != 1 { - return invalid, errors.Errorf("specify exactly one timestamp for %s", filter) + return invalid, errors.Errorf("specify exactly one timestamp for until") } ts, err := timetype.GetTimestamp(filterValues[0], time.Now()) if err != nil { diff --git a/pkg/util/filters_test.go b/pkg/util/filters_test.go index 4771f682b..47259013e 100644 --- a/pkg/util/filters_test.go +++ b/pkg/util/filters_test.go @@ -1,6 +1,8 @@ package util -import "testing" +import ( + "testing" +) func TestMatchLabelFilters(t *testing.T) { testLabels := map[string]string{ @@ -75,3 +77,37 @@ func TestMatchLabelFilters(t *testing.T) { }) } } + +func TestComputeUntilTimestamp(t *testing.T) { + tests := []struct { + name string + args []string + wantErr bool + }{ + { + name: "Return error when more values in list", + args: []string{"5h", "6s"}, + wantErr: true, + }, + { + name: "Return error when invalid time", + args: []string{"invalidTime"}, + wantErr: true, + }, + { + name: "Do not return error when correct time format supplied", + args: []string{"44m"}, + wantErr: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + _, err := ComputeUntilTimestamp(tt.args) + if (err != nil) != tt.wantErr { + t.Errorf("ComputeUntilTimestamp() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} -- cgit v1.2.3-54-g00ecf From 396a59b0229aae6f87cb18eb6d369087eefdaf57 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Wed, 24 Mar 2021 10:51:33 +0100 Subject: libpod/image: unit tests: use `require.NoError` In contrast to `assert.NoError`, `require.NoError` treats mismatches fatally which in many cases is necessary to prevent subsequent checks from segfaulting. Signed-off-by: Valentin Rothberg --- libpod/image/image_test.go | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) (limited to 'libpod') diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index e2518b687..ed1d15ecc 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -14,6 +14,7 @@ import ( "github.com/containers/storage/pkg/reexec" "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var ( @@ -107,7 +108,7 @@ func TestImage_NewFromLocal(t *testing.T) { assert.NoError(t, err) for _, name := range image.names { newImage, err := ir.NewFromLocal(name) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, newImage.ID(), image.img.ID()) } } @@ -142,7 +143,7 @@ func TestImage_New(t *testing.T) { // after the pull for _, img := range names { newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing, nil) - assert.NoError(t, err) + require.NoError(t, err) assert.NotEqual(t, newImage.ID(), "") err = newImage.Remove(context.Background(), false) assert.NoError(t, err) @@ -168,14 +169,14 @@ func TestImage_MatchRepoTag(t *testing.T) { GraphRoot: workdir, } ir, err := NewImageRuntimeFromOptions(so) - assert.NoError(t, err) + require.NoError(t, err) ir.Eventer = events.NewNullEventer() newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, nil, util.PullImageMissing, nil) - assert.NoError(t, err) + require.NoError(t, err) err = newImage.TagImage("foo:latest") - assert.NoError(t, err) + require.NoError(t, err) err = newImage.TagImage("foo:bar") - assert.NoError(t, err) + require.NoError(t, err) // Tests start here. for _, name := range bbNames { @@ -188,12 +189,12 @@ func TestImage_MatchRepoTag(t *testing.T) { // foo should resolve to foo:latest repoTag, err := newImage.MatchRepoTag("foo") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "localhost/foo:latest", repoTag) // foo:bar should resolve to foo:bar repoTag, err = newImage.MatchRepoTag("foo:bar") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "localhost/foo:bar", repoTag) // Shutdown the runtime and remove the temporary storage cleanup(workdir, ir) @@ -202,9 +203,7 @@ func TestImage_MatchRepoTag(t *testing.T) { // TestImage_RepoDigests tests RepoDigest generation. func TestImage_RepoDigests(t *testing.T) { dgst, err := digest.Parse("sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) for _, tt := range []struct { name string @@ -236,10 +235,7 @@ func TestImage_RepoDigests(t *testing.T) { }, } actual, err := image.RepoDigests() - if err != nil { - t.Fatal(err) - } - + require.NoError(t, err) assert.Equal(t, test.expected, actual) image = &Image{ @@ -249,10 +245,7 @@ func TestImage_RepoDigests(t *testing.T) { }, } actual, err = image.RepoDigests() - if err != nil { - t.Fatal(err) - } - + require.NoError(t, err) assert.Equal(t, test.expected, actual) }) } -- cgit v1.2.3-54-g00ecf From 43c772aa2910e7c3c3f839a29ec7dea632a0b300 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Wed, 24 Mar 2021 10:54:42 +0100 Subject: libpod/image: unit tests: defer cleanup Defer cleaning up the test artifacts as early as possible. Signed-off-by: Valentin Rothberg --- libpod/image/image_test.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'libpod') diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index ed1d15ecc..df90c2db3 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -95,6 +95,8 @@ func TestImage_NewFromLocal(t *testing.T) { // Need images to be present for this test ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) + defer cleanup(workdir, ir) + ir.Eventer = events.NewNullEventer() bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing, nil) assert.NoError(t, err) @@ -112,9 +114,6 @@ func TestImage_NewFromLocal(t *testing.T) { assert.Equal(t, newImage.ID(), image.img.ID()) } } - - // Shutdown the runtime and remove the temporary storage - cleanup(workdir, ir) } // TestImage_New tests pulling the image by various names, tags, and from @@ -127,13 +126,14 @@ func TestImage_New(t *testing.T) { var names []string workdir, err := mkWorkDir() assert.NoError(t, err) - so := storage.StoreOptions{ RunRoot: workdir, GraphRoot: workdir, } ir, err := NewImageRuntimeFromOptions(so) assert.NoError(t, err) + defer cleanup(workdir, ir) + ir.Eventer = events.NewNullEventer() // Build the list of pull names names = append(names, bbNames...) @@ -148,9 +148,6 @@ func TestImage_New(t *testing.T) { err = newImage.Remove(context.Background(), false) assert.NoError(t, err) } - - // Shutdown the runtime and remove the temporary storage - cleanup(workdir, ir) } // TestImage_MatchRepoTag tests the various inputs we need to match @@ -163,13 +160,14 @@ func TestImage_MatchRepoTag(t *testing.T) { //Set up workdir, err := mkWorkDir() assert.NoError(t, err) - so := storage.StoreOptions{ RunRoot: workdir, GraphRoot: workdir, } ir, err := NewImageRuntimeFromOptions(so) require.NoError(t, err) + defer cleanup(workdir, ir) + ir.Eventer = events.NewNullEventer() newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, nil, util.PullImageMissing, nil) require.NoError(t, err) @@ -196,8 +194,6 @@ func TestImage_MatchRepoTag(t *testing.T) { repoTag, err = newImage.MatchRepoTag("foo:bar") require.NoError(t, err) assert.Equal(t, "localhost/foo:bar", repoTag) - // Shutdown the runtime and remove the temporary storage - cleanup(workdir, ir) } // TestImage_RepoDigests tests RepoDigest generation. -- cgit v1.2.3-54-g00ecf From c9640bab79865a5fe731e0aaf27f12036ba9fbd5 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Wed, 24 Mar 2021 13:10:51 +0100 Subject: libpod/image: unit tests: use a `registries.conf` for aliases Since some unit tests use "busybox", we need to point it to some alias if we want it to pass CI on F34 where we're running in enforced mode. Furthermore, make sure that the registries.conf can actually be overridden in the code. Signed-off-by: Valentin Rothberg --- libpod/image/image_test.go | 12 +++++++++--- libpod/image/pull.go | 8 +++++++- libpod/image/testdata/registries.conf | 4 ++++ 3 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 libpod/image/testdata/registries.conf (limited to 'libpod') diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index df90c2db3..2b42d6394 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -139,11 +139,14 @@ func TestImage_New(t *testing.T) { names = append(names, bbNames...) writer := os.Stdout + opts := DockerRegistryOptions{ + RegistriesConfPath: "testdata/registries.conf", + } // Iterate over the names and delete the image // after the pull for _, img := range names { - newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing, nil) - require.NoError(t, err) + newImage, err := ir.New(context.Background(), img, "", "", writer, &opts, SigningOptions{}, nil, util.PullImageMissing, nil) + require.NoError(t, err, img) assert.NotEqual(t, newImage.ID(), "") err = newImage.Remove(context.Background(), false) assert.NoError(t, err) @@ -168,8 +171,11 @@ func TestImage_MatchRepoTag(t *testing.T) { require.NoError(t, err) defer cleanup(workdir, ir) + opts := DockerRegistryOptions{ + RegistriesConfPath: "testdata/registries.conf", + } ir.Eventer = events.NewNullEventer() - newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, nil, util.PullImageMissing, nil) + newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, &opts, SigningOptions{}, nil, util.PullImageMissing, nil) require.NoError(t, err) err = newImage.TagImage("foo:latest") require.NoError(t, err) diff --git a/libpod/image/pull.go b/libpod/image/pull.go index 58160b52f..6517fbd07 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -245,6 +245,7 @@ func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName s sc.OSChoice = dockerOptions.OSChoice sc.ArchitectureChoice = dockerOptions.ArchitectureChoice sc.VariantChoice = dockerOptions.VariantChoice + sc.SystemRegistriesConfPath = dockerOptions.RegistriesConfPath } if signaturePolicyPath == "" { sc.SignaturePolicyPath = ir.SignaturePolicyPath @@ -306,7 +307,12 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa } }() - systemRegistriesConfPath := registries.SystemRegistriesConfPath() + var systemRegistriesConfPath string + if dockerOptions != nil && dockerOptions.RegistriesConfPath != "" { + systemRegistriesConfPath = dockerOptions.RegistriesConfPath + } else { + systemRegistriesConfPath = registries.SystemRegistriesConfPath() + } var ( images []string diff --git a/libpod/image/testdata/registries.conf b/libpod/image/testdata/registries.conf new file mode 100644 index 000000000..16622a1ac --- /dev/null +++ b/libpod/image/testdata/registries.conf @@ -0,0 +1,4 @@ +short-name-mode="enforcing" + +[aliases] +"busybox"="docker.io/library/busybox" -- cgit v1.2.3-54-g00ecf From 633ae014e6945670676e4118356d09418c678138 Mon Sep 17 00:00:00 2001 From: Jordan Williams Date: Wed, 17 Mar 2021 13:39:33 -0500 Subject: Generate Kubernetes PersistentVolumeClaims from named volumes Fixes #5788 This commit adds support for named volumes in podman-generate-kube. Named volumes are output in the YAML as PersistentVolumeClaims. To avoid naming conflicts, the volume name is suffixed with "-pvc". This commit adds a corresponding suffix for host path mounts. Host path volumes are suffixed with "-host". Signed-off-by: Jordan Williams --- docs/source/markdown/podman-generate-kube.1.md | 115 ++++++++++++++++++++++++- docs/source/markdown/podman-play-kube.1.md | 4 +- libpod/kube.go | 32 ++++++- test/e2e/generate_kube_test.go | 30 +++++++ 4 files changed, 174 insertions(+), 7 deletions(-) (limited to 'libpod') diff --git a/docs/source/markdown/podman-generate-kube.1.md b/docs/source/markdown/podman-generate-kube.1.md index 019bae539..0e5a31ae6 100644 --- a/docs/source/markdown/podman-generate-kube.1.md +++ b/docs/source/markdown/podman-generate-kube.1.md @@ -6,10 +6,14 @@ podman-generate-kube - Generate Kubernetes YAML based on a pod or container **podman generate kube** [*options*] *container...* | *pod* ## DESCRIPTION -**podman generate kube** will generate Kubernetes Pod YAML (v1 specification) from Podman one or more containers or a single pod. Whether +**podman generate kube** will generate Kubernetes Pod YAML (v1 specification) from Podman from one or more containers or a single pod. Whether the input is for containers or a pod, Podman will always generate the specification as a Pod. The input may be in the form of a pod or one or more container names or IDs. +Volumes appear in the generated YAML according to two different volume types. Bind-mounted volumes become *hostPath* volume types and named volumes become *persistentVolumeClaim* volume types. Generated *hostPath* volume types will be one of three subtypes depending on the state of the host path: *DirectoryOrCreate* when no file or directory exists at the host, *Directory* when host path is a directory, or *File* when host path is a file. The value for *claimName* for a *persistentVolumeClaim* is the name of the named volume registered in Podman. + +Potential name conflicts between volumes are avoided by using a standard naming scheme for each volume type. The *hostPath* volume types are named according to the path on the host machine, replacing forward slashes with hyphens less any leading and trailing forward slashes. The special case of the filesystem root, `/`, translates to the name `root`. Additionally, the name is suffixed with `-host` to avoid naming conflicts with *persistentVolumeClaim* volumes. Each *persistentVolumeClaim* volume type uses the name of its associated named volume suffixed with `-pvc`. + Note that the generated Kubernetes YAML file can be used to re-run the deployment via podman-play-kube(1). ## OPTIONS @@ -25,7 +29,7 @@ random port is assigned by Podman in the specification. ## EXAMPLES -Create Kubernetes Pod YAML for a container called `some-mariadb` . +Create Kubernetes Pod YAML for a container called `some-mariadb`. ``` $ sudo podman generate kube some-mariadb # Generation of Kubernetes YAML is still under development! @@ -81,6 +85,113 @@ spec: status: {} ``` +Create Kubernetes Pod YAML for a container with the directory `/home/user/my-data` on the host bind-mounted in the container to `/volume`. +``` +$ podman generate kube my-container-with-bind-mounted-data +# Generation of Kubernetes YAML is still under development! +# +# Save the output of this file and use kubectl create -f to import +# it into Kubernetes. +# +# Created with podman-3.1.0-dev +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: "2021-03-18T16:26:08Z" + labels: + app: my-container-with-bind-mounted-data + name: my-container-with-bind-mounted-data +spec: + containers: + - command: + - /bin/sh + env: + - name: PATH + value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + - name: TERM + value: xterm + - name: container + value: podman + image: docker.io/library/alpine:latest + name: test-bind-mount + resources: {} + securityContext: + allowPrivilegeEscalation: true + capabilities: + drop: + - CAP_MKNOD + - CAP_NET_RAW + - CAP_AUDIT_WRITE + privileged: false + readOnlyRootFilesystem: false + seLinuxOptions: {} + volumeMounts: + - mountPath: /volume + name: home-user-my-data-host + workingDir: / + dnsConfig: {} + restartPolicy: Never + volumes: + - hostPath: + path: /home/user/my-data + type: Directory + name: home-user-my-data-host +status: {} +``` + +Create Kubernetes Pod YAML for a container with the named volume `priceless-data` mounted in the container at `/volume`. +``` +$ podman generate kube my-container-using-priceless-data +# Generation of Kubernetes YAML is still under development! +# +# Save the output of this file and use kubectl create -f to import +# it into Kubernetes. +# +# Created with podman-3.1.0-dev +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: "2021-03-18T16:26:08Z" + labels: + app: my-container-using-priceless-data + name: my-container-using-priceless-data +spec: + containers: + - command: + - /bin/sh + env: + - name: PATH + value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + - name: TERM + value: xterm + - name: container + value: podman + image: docker.io/library/alpine:latest + name: test-bind-mount + resources: {} + securityContext: + allowPrivilegeEscalation: true + capabilities: + drop: + - CAP_MKNOD + - CAP_NET_RAW + - CAP_AUDIT_WRITE + privileged: false + readOnlyRootFilesystem: false + seLinuxOptions: {} + volumeMounts: + - mountPath: /volume + name: priceless-data-pvc + workingDir: / + dnsConfig: {} + restartPolicy: Never + volumes: + - name: priceless-data-pvc + persistentVolumeClaim: + claimName: priceless-data +status: {} +``` + Create Kubernetes Pod YAML for a pod called `demoweb` and include a service. ``` $ sudo podman generate kube -s demoweb diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md index 0a34a622f..1be597b94 100644 --- a/docs/source/markdown/podman-play-kube.1.md +++ b/docs/source/markdown/podman-play-kube.1.md @@ -11,7 +11,9 @@ podman-play-kube - Create pods and containers based on Kubernetes YAML Ideally the input file would be one created by Podman (see podman-generate-kube(1)). This would guarantee a smooth import and expected results. -Note: HostPath volume types created by play kube will be given an SELinux private label (Z) +Only two volume types are supported by play kube, the *hostPath* and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, and *Socket* subtypes are supported. The *CharDevice* and *BlockDevice* subtypes are not supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume. + +Note: *hostPath* volume types created by play kube will be given an SELinux private label (Z) Note: If the `:latest` tag is used, Podman will attempt to pull the image from a registry. If the image was built locally with Podman or Buildah, it will have `localhost` as the domain, in that case, Podman will use the image from the local store even if it has the `:latest` tag. diff --git a/libpod/kube.go b/libpod/kube.go index 407c4ae00..b4dd4f10a 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -330,8 +330,6 @@ func containerToV1Container(c *Container) (v1.Container, []v1.Volume, *v1.PodDNS } if len(c.config.UserVolumes) > 0 { - // TODO When we until we can resolve what the volume name should be, this is disabled - // Volume names need to be coordinated "globally" in the kube files. volumeMounts, volumes, err := libpodMountsToKubeVolumeMounts(c) if err != nil { return kubeContainer, kubeVolumes, nil, err @@ -493,8 +491,7 @@ func libpodEnvVarsToKubeEnvVars(envs []string) ([]v1.EnvVar, error) { // libpodMountsToKubeVolumeMounts converts the containers mounts to a struct kube understands func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, []v1.Volume, error) { - // TODO when named volumes are supported in play kube, also parse named volumes here - _, mounts := c.sortUserVolumes(c.config.Spec) + namedVolumes, mounts := c.sortUserVolumes(c.config.Spec) vms := make([]v1.VolumeMount, 0, len(mounts)) vos := make([]v1.Volume, 0, len(mounts)) for _, m := range mounts { @@ -505,9 +502,34 @@ func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, []v1.Volume vms = append(vms, vm) vos = append(vos, vo) } + for _, v := range namedVolumes { + vm, vo := generateKubePersistentVolumeClaim(v) + vms = append(vms, vm) + vos = append(vos, vo) + } return vms, vos, nil } +// generateKubePersistentVolumeClaim converts a ContainerNamedVolume to a Kubernetes PersistentVolumeClaim +func generateKubePersistentVolumeClaim(v *ContainerNamedVolume) (v1.VolumeMount, v1.Volume) { + ro := util.StringInSlice("ro", v.Options) + + // To avoid naming conflicts with any host path mounts, add a unique suffix to the volume's name. + name := v.Name + "-pvc" + + vm := v1.VolumeMount{} + vm.Name = name + vm.MountPath = v.Dest + vm.ReadOnly = ro + + pvc := v1.PersistentVolumeClaimVolumeSource{ClaimName: v.Name, ReadOnly: ro} + vs := v1.VolumeSource{} + vs.PersistentVolumeClaim = &pvc + vo := v1.Volume{Name: name, VolumeSource: vs} + + return vm, vo +} + // generateKubeVolumeMount takes a user specified mount and returns // a kubernetes VolumeMount (to be added to the container) and a kubernetes Volume // (to be added to the pod) @@ -519,6 +541,8 @@ func generateKubeVolumeMount(m specs.Mount) (v1.VolumeMount, v1.Volume, error) { if err != nil { return vm, vo, err } + // To avoid naming conflicts with any persistent volume mounts, add a unique suffix to the volume's name. + name += "-host" vm.Name = name vm.MountPath = m.Destination if util.StringInSlice("ro", m.Options) { diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index e74192746..1c53307bd 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -495,6 +495,36 @@ var _ = Describe("Podman generate kube", func() { Expect(inspect.OutputToString()).To(ContainSubstring(vol1)) }) + It("podman generate kube with persistent volume claim", func() { + vol := "vol-test-persistent-volume-claim" + + // we need a container name because IDs don't persist after rm/play + ctrName := "test-persistent-volume-claim" + ctrNameInKubePod := "test1-test-persistent-volume-claim" + + session := podmanTest.Podman([]string{"run", "-d", "--pod", "new:test1", "--name", ctrName, "-v", vol + ":/volume/:z", "alpine", "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + outputFile := filepath.Join(podmanTest.RunRoot, "pod.yaml") + kube := podmanTest.Podman([]string{"generate", "kube", "test1", "-f", outputFile}) + kube.WaitWithDefaultTimeout() + Expect(kube.ExitCode()).To(Equal(0)) + + rm := podmanTest.Podman([]string{"pod", "rm", "-f", "test1"}) + rm.WaitWithDefaultTimeout() + Expect(rm.ExitCode()).To(Equal(0)) + + play := podmanTest.Podman([]string{"play", "kube", outputFile}) + play.WaitWithDefaultTimeout() + Expect(play.ExitCode()).To(Equal(0)) + + inspect := podmanTest.Podman([]string{"inspect", ctrNameInKubePod}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(Equal(0)) + Expect(inspect.OutputToString()).To(ContainSubstring(vol)) + }) + It("podman generate kube sharing pid namespace", func() { podName := "test" podSession := podmanTest.Podman([]string{"pod", "create", "--name", podName, "--share", "pid"}) -- cgit v1.2.3-54-g00ecf From 52cd3ce2d9e170d5c20246372cc1985d82b3533d Mon Sep 17 00:00:00 2001 From: TomSweeneyRedHat Date: Sat, 27 Feb 2021 19:53:03 -0500 Subject: Validate passed in timezone from tz option Erik Sjolund reported an issue where a badly formated file could be passed into the `--tz` option and then the date in the container would be badly messed up: ``` erik@laptop:~$ echo Hello > file.txt erik@laptop:~$ podman run --tz=../../../home/erik/file.txt --rm -ti docker.io/library/alpine cat /etc/localtime Hello erik@laptop:~$ podman --version podman version 3.0.0-rc1 erik@laptop:~$ ``` This fix checks to make sure the TZ passed in is a valid value and then proceeds with the rest of the processing. This was first reported as a potential security issue, but it was thought not to be. However, I thought closing the hole sooner rather than later would be good. Signed-off-by: TomSweeneyRedHat --- libpod/container_internal_linux.go | 14 +++++++++++--- test/e2e/run_test.go | 23 ++++++++++++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) (limited to 'libpod') diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 0fd29e456..a136fb72d 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -1509,16 +1509,24 @@ func (c *Container) makeBindMounts() error { } // Make /etc/localtime - if c.Timezone() != "" { + ctrTimezone := c.Timezone() + if ctrTimezone != "" { + // validate the format of the timezone specified if it's not "local" + if ctrTimezone != "local" { + _, err = time.LoadLocation(ctrTimezone) + if err != nil { + return errors.Wrapf(err, "error finding timezone for container %s", c.ID()) + } + } if _, ok := c.state.BindMounts["/etc/localtime"]; !ok { var zonePath string - if c.Timezone() == "local" { + if ctrTimezone == "local" { zonePath, err = filepath.EvalSymlinks("/etc/localtime") if err != nil { return errors.Wrapf(err, "error finding local timezone for container %s", c.ID()) } } else { - zone := filepath.Join("/usr/share/zoneinfo", c.Timezone()) + zone := filepath.Join("/usr/share/zoneinfo", ctrTimezone) zonePath, err = filepath.EvalSymlinks(zone) if err != nil { return errors.Wrapf(err, "error setting timezone for container %s", c.ID()) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 53a304aec..23930b4f7 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1412,7 +1412,28 @@ USER mail`, BB) }) It("podman run --tz", func() { - session := podmanTest.Podman([]string{"run", "--tz", "foo", "--rm", ALPINE, "date"}) + testDir := filepath.Join(podmanTest.RunRoot, "tz-test") + err := os.MkdirAll(testDir, 0755) + Expect(err).To(BeNil()) + + tzFile := filepath.Join(testDir, "tzfile.txt") + file, err := os.Create(tzFile) + Expect(err).To(BeNil()) + + _, err = file.WriteString("Hello") + Expect(err).To(BeNil()) + file.Close() + + badTZFile := fmt.Sprintf("../../../%s", tzFile) + session := podmanTest.Podman([]string{"run", "--tz", badTZFile, "--rm", ALPINE, "date"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session.ErrorToString()).To(ContainSubstring("error finding timezone for container")) + + err = os.Remove(tzFile) + Expect(err).To(BeNil()) + + session = podmanTest.Podman([]string{"run", "--tz", "foo", "--rm", ALPINE, "date"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Not(Equal(0))) -- cgit v1.2.3-54-g00ecf