diff options
48 files changed, 703 insertions, 216 deletions
diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index 208d5d58f..11e8f6870 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -5,6 +5,7 @@ import ( "os" tm "github.com/buger/goterm" + "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" @@ -16,7 +17,6 @@ import ( "github.com/containers/podman/v3/utils" "github.com/docker/go-units" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -55,6 +55,7 @@ type statsOptionsCLI struct { Latest bool NoReset bool NoStream bool + Interval int } var ( @@ -72,6 +73,9 @@ func statFlags(cmd *cobra.Command) { flags.BoolVar(&statsOptions.NoReset, "no-reset", false, "Disable resetting the screen between intervals") flags.BoolVar(&statsOptions.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false") + intervalFlagName := "interval" + flags.IntVarP(&statsOptions.Interval, intervalFlagName, "i", 5, "Time in seconds between stats reports") + _ = cmd.RegisterFlagCompletionFunc(intervalFlagName, completion.AutocompleteNone) } func init() { @@ -122,8 +126,9 @@ func stats(cmd *cobra.Command, args []string) error { // Convert to the entities options. We should not leak CLI-only // options into the backend and separate concerns. opts := entities.ContainerStatsOptions{ - Latest: statsOptions.Latest, - Stream: !statsOptions.NoStream, + Latest: statsOptions.Latest, + Stream: !statsOptions.NoStream, + Interval: statsOptions.Interval, } statsChan, err := registry.ContainerEngine().ContainerStats(registry.Context(), args, opts) if err != nil { @@ -134,7 +139,7 @@ func stats(cmd *cobra.Command, args []string) error { return report.Error } if err := outputStats(report.Stats); err != nil { - logrus.Error(err) + return err } } return nil diff --git a/cmd/podman/images/scp.go b/cmd/podman/images/scp.go index 71399e0b7..a47d01995 100644 --- a/cmd/podman/images/scp.go +++ b/cmd/podman/images/scp.go @@ -111,7 +111,7 @@ func scp(cmd *cobra.Command, args []string) (finalErr error) { if err != nil { return err } - fmt.Println("Loaded images(s): " + strings.Join(report.Names, ",")) + fmt.Println("Loaded image(s): " + strings.Join(report.Names, ",")) case scpOpts.ToRemote: // remote host load scpOpts.Save.Format = "oci-archive" abiErr := abiEng.Save(context.Background(), scpOpts.SourceImageName, []string{}, scpOpts.Save) // save the image locally before loading it on remote, local, or ssh @@ -137,7 +137,7 @@ func scp(cmd *cobra.Command, args []string) (finalErr error) { if err != nil { return err } - fmt.Println("Loaded images(s): " + strings.Join(rep.Names, ",")) + fmt.Println("Loaded image(s): " + strings.Join(rep.Names, ",")) } return nil } diff --git a/docs/source/markdown/podman-image-scp.1.md b/docs/source/markdown/podman-image-scp.1.md index 7d143c851..420452a4d 100644 --- a/docs/source/markdown/podman-image-scp.1.md +++ b/docs/source/markdown/podman-image-scp.1.md @@ -59,7 +59,7 @@ Copying blob 9450ef9feb15 [--------------------------------------] 0.0b / 0.0b Copying config 1f97f0559c done Writing manifest to image destination Storing signatures -Loaded images(s): docker.io/library/alpine:latest +Loaded image(s): docker.io/library/alpine:latest ``` ## SEE ALSO diff --git a/docs/source/markdown/podman-network-connect.1.md b/docs/source/markdown/podman-network-connect.1.md index cff4336d6..47a54bd33 100644 --- a/docs/source/markdown/podman-network-connect.1.md +++ b/docs/source/markdown/podman-network-connect.1.md @@ -10,8 +10,6 @@ podman\-network\-connect - Connect a container to a network Connects a container to a network. A container can be connected to a network by name or by ID. Once connected, the container can communicate with other containers in the same network. -This command is not available for rootless users. - ## OPTIONS #### **--alias** Add network-scoped alias for the container. If the network is using the `dnsname` CNI plugin, these aliases diff --git a/docs/source/markdown/podman-network-disconnect.1.md b/docs/source/markdown/podman-network-disconnect.1.md index 8b7125282..a13aa6088 100644 --- a/docs/source/markdown/podman-network-disconnect.1.md +++ b/docs/source/markdown/podman-network-disconnect.1.md @@ -7,9 +7,10 @@ podman\-network\-disconnect - Disconnect a container from a network **podman network disconnect** [*options*] network container ## DESCRIPTION -Disconnects a container from a network. +Disconnects a container from a network. A container can be disconnected from a network by name or by ID. +If all networks are disconnected from the container, it will behave like a container created with `--network=none` +and it will longer have network connectivity until a network is connected again. -This command is not available for rootless users. ## OPTIONS #### **--force**, **-f** diff --git a/docs/source/markdown/podman-network-reload.1.md b/docs/source/markdown/podman-network-reload.1.md index 1d9f34f2e..593265df6 100644 --- a/docs/source/markdown/podman-network-reload.1.md +++ b/docs/source/markdown/podman-network-reload.1.md @@ -13,8 +13,6 @@ Rootfull Podman relies on iptables rules in order to provide network connectivit this happens for example with `firewall-cmd --reload`, the container loses network connectivity. This command restores the network connectivity. -This command is not available for rootless users since rootless containers are not affected by such connectivity problems. - ## OPTIONS #### **--all**, **-a** diff --git a/docs/source/markdown/podman-stats.1.md b/docs/source/markdown/podman-stats.1.md index 300106796..abd8fd530 100644 --- a/docs/source/markdown/podman-stats.1.md +++ b/docs/source/markdown/podman-stats.1.md @@ -37,6 +37,10 @@ Do not clear the terminal/screen in between reporting intervals Disable streaming stats and only pull the first result, default setting is false +#### **--interval**=*seconds*, **-i**=*seconds* + +Time in seconds between stats reports, defaults to 5 seconds. + #### **--format**=*template* Pretty-print container statistics to JSON or using a Go template diff --git a/docs/source/markdown/podman-volume-create.1.md b/docs/source/markdown/podman-volume-create.1.md index a06411000..9bf5a3d81 100644 --- a/docs/source/markdown/podman-volume-create.1.md +++ b/docs/source/markdown/podman-volume-create.1.md @@ -17,7 +17,7 @@ driver options can be set using the **--opt** flag. #### **--driver**=*driver* -Specify the volume driver name (default **local**). Setting this to a value other than **local** Podman will attempt to create the volume using a volume plugin with the given name. Such plugins must be defined in the **volume_plugins** section of the **containers.conf**(5) configuration file. +Specify the volume driver name (default **local**). Setting this to a value other than **local** Podman attempts to create the volume using a volume plugin with the given name. Such plugins must be defined in the **volume_plugins** section of the **containers.conf**(5) configuration file. #### **--help** @@ -34,10 +34,14 @@ For the default driver, **local**, this allows a volume to be configured to moun For the `local` driver the following options are supported: `type`, `device`, and `o`. The `type` option sets the type of the filesystem to be mounted, and is equivalent to the `-t` flag to **mount(8)**. The `device` option sets the device to be mounted, and is equivalent to the `device` argument to **mount(8)**. -The `o` option sets options for the mount, and is equivalent to the `-o` flag to **mount(8)** with two exceptions. -The `o` option supports `uid` and `gid` options to set the UID and GID of the created volume that are not normally supported by **mount(8)**. -Using volume options with the **local** driver requires root privileges. -When not using the **local** driver, the given options will be passed directly to the volume plugin. In this case, supported options will be dictated by the plugin in question, not Podman. + +The `o` option sets options for the mount, and is equivalent to the `-o` flag to **mount(8)** with these exceptions: + + - The `o` option supports `uid` and `gid` options to set the UID and GID of the created volume that are not normally supported by **mount(8)**. + - The `o` option supports the `size` option to set the maximum size of the created volume and the `inodes` option to set the maximum number of inodes for the volume. Currently these flags are only supported on "xfs" file system mounted with the `prjquota` flag described in the **xfs_quota(8)** man page. + - Using volume options other then the UID/GID options with the **local** driver requires root privileges. + +When not using the **local** driver, the given options are passed directly to the volume plugin. In this case, supported options are dictated by the plugin in question, not Podman. ## EXAMPLES @@ -53,8 +57,36 @@ $ podman volume create --label foo=bar myvol # podman volume create --opt device=tmpfs --opt type=tmpfs --opt o=uid=1000,gid=1000 testvol ``` +## QUOTAS + +podman volume create uses `XFS project quota controls` for controlling the size and the number of inodes of builtin volumes. The directory used to store the volumes must be an`XFS` file system and be mounted with the `pquota` option. + +Example /etc/fstab entry: +``` +/dev/podman/podman-var /var xfs defaults,x-systemd.device-timeout=0,pquota 1 2 +``` + +Podman generates project ids for each builtin volume, but these project ids need to be unique for the XFS file system. These project ids by default are generated randomly, with a potential for overlap with other quotas on the same file +system. + +The xfs_quota tool can be used to assign a project id to the storage driver directory, e.g.: + +``` +echo 100000:/var/lib/containers/storage/overlay >> /etc/projects +echo 200000:/var/lib/containers/storage/volumes >> /etc/projects +echo storage:100000 >> /etc/projid +echo volumes:200000 >> /etc/projid +xfs_quota -x -c 'project -s storage volumes' /<xfs mount point> +``` + +In the example above we are configuring the overlay storage driver for newly +created containers as well as volumes to use project ids with a **start offset**. +All containers will be assigned larger project ids (e.g. >= 100000). +All volume assigned project ids larger project ids starting with 200000. +This prevents xfs_quota management conflicts with containers/storage. + ## SEE ALSO -**podman-volume**(1), **mount**(8), **containers.conf**(5) +**podman-volume**(1), **mount**(8), **containers.conf**(5), **xfs_quota**(8), `xfs_quota(8)`, `projects(5)`, `projid(5)` ## HISTORY January 2020, updated with information on volume plugins by Matthew Heon <mheon@redhat.com> @@ -25,7 +25,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/digitalocean/go-qemu v0.0.0-20210209191958-152a1535e49f github.com/docker/distribution v2.7.1+incompatible - github.com/docker/docker v20.10.7+incompatible + github.com/docker/docker v20.10.8+incompatible github.com/docker/go-connections v0.4.0 github.com/docker/go-plugins-helpers v0.0.0-20200102110956-c9a8a2d92ccc github.com/docker/go-units v0.4.0 @@ -50,10 +50,10 @@ require ( github.com/opencontainers/runc v1.0.1 github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 github.com/opencontainers/runtime-tools v0.9.0 - github.com/opencontainers/selinux v1.8.2 + github.com/opencontainers/selinux v1.8.3 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 - github.com/rootless-containers/rootlesskit v0.14.2 + github.com/rootless-containers/rootlesskit v0.14.4 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.2.1 github.com/spf13/pflag v1.0.5 @@ -322,8 +322,9 @@ github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ= github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.8+incompatible h1:RVqD337BgQicVCzYrrlhLDWhq6OAD2PJDUg2LsEUvKM= +github.com/docker/docker v20.10.8+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -403,7 +404,7 @@ github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -555,7 +556,6 @@ github.com/insomniacslk/dhcp v0.0.0-20210120172423-cc9239ac6294/go.mod h1:TKl4jN github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee h1:PAXLXk1heNZ5yokbMBpVLZQxo43wCZxRwl00mX+dd44= github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= -github.com/jamescun/tuntap v0.0.0-20190712092105-cb1fb277045c/go.mod h1:zzwpsgcYhzzIP5WyF8g9ivCv38cY9uAV9Gu0m3lThhE= github.com/jinzhu/copier v0.3.2 h1:QdBOCbaouLDYaIPFfi1bKv5F5tPpeTwXe4sD0jqtz5w= github.com/jinzhu/copier v0.3.2/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -750,8 +750,9 @@ github.com/opencontainers/runtime-tools v0.9.0/go.mod h1:r3f7wjNzSs2extwzU3Y+6pK github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/opencontainers/selinux v1.8.2 h1:c4ca10UMgRcvZ6h0K4HtS15UaVSBEaE+iln2LVpAuGc= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opencontainers/selinux v1.8.3 h1:tzZR7AuKB5gU1+53uBkoG4XdIFGZzvJTOVoNbRQI8/4= +github.com/opencontainers/selinux v1.8.3/go.mod h1:HTvjPFoGMbpQsG886e3lQwnsRWtE4TC1OF3OUvG9FAo= github.com/openshift/imagebuilder v1.2.2-0.20210415181909-87f3e48c2656 h1:WaxyNFpmIDu4i6so9r6LVFIbSaXqsj8oitMitt86ae4= github.com/openshift/imagebuilder v1.2.2-0.20210415181909-87f3e48c2656/go.mod h1:9aJRczxCH0mvT6XQ+5STAQaPWz7OsWcU5/mRkt8IWeo= github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw= @@ -812,8 +813,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rootless-containers/rootlesskit v0.14.2 h1:jmsSyNyRG0QdWc3usppt5jEy5qOheeUsIINcymPrOFg= -github.com/rootless-containers/rootlesskit v0.14.2/go.mod h1:nV3TpRISvwhZQSwo0nmQQnxjCxXr3mvrMi0oASLvzcg= +github.com/rootless-containers/rootlesskit v0.14.4 h1:pqx9a+OC/6jjV7sIUKy3D1p6NLEC6WIMiJWAGsGMCUM= +github.com/rootless-containers/rootlesskit v0.14.4/go.mod h1:Ai3detLzryb/4EkzXmNfh8aByUcBXp/qqkQusJs1SO8= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -839,6 +840,7 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index bff64aa95..f30f622ac 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -1912,6 +1912,7 @@ func (c *Container) appendHosts(path string, netCtr *Container) (string, error) // and returns a string in a format that can be written to the host file func (c *Container) getHosts() string { var hosts string + if len(c.config.HostAdd) > 0 { for _, host := range c.config.HostAdd { // the host format has already been verified at this point @@ -1922,36 +1923,33 @@ func (c *Container) getHosts() string { hosts += c.cniHosts() - // If not making a network namespace, add our own hostname. + // Add hostname for slirp4netns if c.Hostname() != "" { if c.config.NetMode.IsSlirp4netns() { // When using slirp4netns, the interface gets a static IP slirp4netnsIP, err := GetSlirp4netnsIP(c.slirp4netnsSubnet) if err != nil { - logrus.Warn("failed to determine slirp4netnsIP: ", err.Error()) + logrus.Warnf("failed to determine slirp4netnsIP: %v", err.Error()) } else { hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", slirp4netnsIP.String(), c.Hostname(), c.config.Name) } - } else { - hasNetNS := false - netNone := false - for _, ns := range c.config.Spec.Linux.Namespaces { - if ns.Type == spec.NetworkNamespace { - hasNetNS = true - if ns.Path == "" && !c.config.CreateNetNS { - netNone = true - } - break + } + + // Do we have a network namespace? + netNone := false + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.NetworkNamespace { + if ns.Path == "" && !c.config.CreateNetNS { + netNone = true } + break } - if !hasNetNS { - // 127.0.1.1 and host's hostname to match Docker - osHostname, _ := os.Hostname() - hosts += fmt.Sprintf("127.0.1.1 %s %s %s\n", osHostname, c.Hostname(), c.config.Name) - } - if netNone { - hosts += fmt.Sprintf("127.0.1.1 %s %s\n", c.Hostname(), c.config.Name) - } + } + + // If we are net=none (have a network namespace, but not connected to + // anything) add the container's name and hostname to localhost. + if netNone { + hosts += fmt.Sprintf("127.0.0.1 %s %s\n", c.Hostname(), c.config.Name) } } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 0f3e03e06..8e9b5997c 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -1214,7 +1214,29 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro } } c.state.NetworkStatus = tmpNetworkStatus - return c.save() + err = c.save() + if err != nil { + return err + } + + // OCICNI will set the loopback adpter down on teardown so we should set it up again + err = c.state.NetNS.Do(func(_ ns.NetNS) error { + link, err := netlink.LinkByName("lo") + if err != nil { + return err + } + err = netlink.LinkSetUp(link) + return err + }) + if err != nil { + logrus.Warnf("failed to set loopback adpter up in the container: %v", err) + } + // Reload ports when there are still connected networks, maybe we removed the network interface with the child ip. + // Reloading without connected networks does not make sense, so we can skip this step. + if rootless.IsRootless() && len(tmpNetworkStatus) > 0 { + return c.reloadRootlessRLKPortMapping() + } + return nil } // ConnectNetwork connects a container to a given network @@ -1306,7 +1328,16 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e networkStatus[index] = networkResults[0] c.state.NetworkStatus = networkStatus } - return c.save() + err = c.save() + if err != nil { + return err + } + // The first network needs a port reload to set the correct child ip for the rootlessport process. + // Adding a second network does not require a port reload because the child ip is still valid. + if rootless.IsRootless() && len(networks) == 0 { + return c.reloadRootlessRLKPortMapping() + } + return nil } // DisconnectContainerFromNetwork removes a container from its CNI network diff --git a/libpod/networking_slirp4netns.go b/libpod/networking_slirp4netns.go index 410b377ec..5858364ff 100644 --- a/libpod/networking_slirp4netns.go +++ b/libpod/networking_slirp4netns.go @@ -17,6 +17,7 @@ import ( "time" "github.com/containers/podman/v3/pkg/errorhandling" + "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/pkg/rootlessport" "github.com/containers/podman/v3/pkg/servicereaper" "github.com/pkg/errors" @@ -466,29 +467,16 @@ func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath strin } } - slirp4netnsIP, err := GetSlirp4netnsIP(ctr.slirp4netnsSubnet) - if err != nil { - return errors.Wrapf(err, "failed to get slirp4ns ip") - } - childIP := slirp4netnsIP.String() -outer: - for _, r := range ctr.state.NetworkStatus { - for _, i := range r.IPs { - ipv4 := i.Address.IP.To4() - if ipv4 != nil { - childIP = ipv4.String() - break outer - } - } - } - + childIP := getRootlessPortChildIP(ctr) cfg := rootlessport.Config{ - Mappings: ctr.config.PortMappings, - NetNSPath: netnsPath, - ExitFD: 3, - ReadyFD: 4, - TmpDir: ctr.runtime.config.Engine.TmpDir, - ChildIP: childIP, + Mappings: ctr.config.PortMappings, + NetNSPath: netnsPath, + ExitFD: 3, + ReadyFD: 4, + TmpDir: ctr.runtime.config.Engine.TmpDir, + ChildIP: childIP, + ContainerID: ctr.config.ID, + RootlessCNI: ctr.config.NetMode.IsBridge() && rootless.IsRootless(), } cfgJSON, err := json.Marshal(cfg) if err != nil { @@ -617,3 +605,62 @@ func (r *Runtime) setupRootlessPortMappingViaSlirp(ctr *Container, cmd *exec.Cmd logrus.Debug("slirp4netns port-forwarding setup via add_hostfwd is ready") return nil } + +func getRootlessPortChildIP(c *Container) string { + if c.config.NetMode.IsSlirp4netns() { + slirp4netnsIP, err := GetSlirp4netnsIP(c.slirp4netnsSubnet) + if err != nil { + return "" + } + return slirp4netnsIP.String() + } + + for _, r := range c.state.NetworkStatus { + for _, i := range r.IPs { + ipv4 := i.Address.IP.To4() + if ipv4 != nil { + return ipv4.String() + } + } + } + return "" +} + +// reloadRootlessRLKPortMapping will trigger a reload for the port mappings in the rootlessport process. +// This should only be called by network connect/disconnect and only as rootless. +func (c *Container) reloadRootlessRLKPortMapping() error { + childIP := getRootlessPortChildIP(c) + logrus.Debugf("reloading rootless ports for container %s, childIP is %s", c.config.ID, childIP) + + var conn net.Conn + var err error + // try three times to connect to the socket, maybe it is not ready yet + for i := 0; i < 3; i++ { + conn, err = net.Dial("unix", filepath.Join(c.runtime.config.Engine.TmpDir, "rp", c.config.ID)) + if err == nil { + break + } + time.Sleep(250 * time.Millisecond) + } + if err != nil { + // This is not a hard error for backwards compatibility. A container started + // with an old version did not created the rootlessport socket. + logrus.Warnf("Could not reload rootless port mappings, port forwarding may no longer work correctly: %v", err) + return nil + } + defer conn.Close() + enc := json.NewEncoder(conn) + err = enc.Encode(childIP) + if err != nil { + return errors.Wrap(err, "port reloading failed") + } + b, err := ioutil.ReadAll(conn) + if err != nil { + return errors.Wrap(err, "port reloading failed") + } + data := string(b) + if data != "OK" { + return errors.Errorf("port reloading failed: %s", data) + } + return nil +} diff --git a/libpod/options.go b/libpod/options.go index 17a36008d..b021b9f50 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1645,6 +1645,32 @@ func WithVolumeUID(uid int) VolumeCreateOption { } } +// WithVolumeSize sets the maximum size of the volume +func WithVolumeSize(size uint64) VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.Size = size + + return nil + } +} + +// WithVolumeInodes sets the maximum inodes of the volume +func WithVolumeInodes(inodes uint64) VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return define.ErrVolumeFinalized + } + + volume.config.Inodes = inodes + + return nil + } +} + // WithVolumeGID sets the GID that the volume will be created as. func WithVolumeGID(gid int) VolumeCreateOption { return func(volume *Volume) error { diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go index 3d5bc8bb2..40df98d7c 100644 --- a/libpod/runtime_volume_linux.go +++ b/libpod/runtime_volume_linux.go @@ -12,6 +12,7 @@ import ( "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/events" volplugin "github.com/containers/podman/v3/libpod/plugin" + "github.com/containers/storage/drivers/quota" "github.com/containers/storage/pkg/stringid" pluginapi "github.com/docker/go-plugins-helpers/volume" "github.com/pkg/errors" @@ -68,7 +69,7 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) // Validate options for key := range volume.config.Options { switch key { - case "device", "o", "type", "UID", "GID": + case "device", "o", "type", "UID", "GID", "SIZE", "INODES": // Do nothing, valid keys default: return nil, errors.Wrapf(define.ErrInvalidArg, "invalid mount option %s for driver 'local'", key) @@ -106,6 +107,26 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) if err := LabelVolumePath(fullVolPath); err != nil { return nil, err } + projectQuotaSupported := false + + q, err := quota.NewControl(r.config.Engine.VolumePath) + if err == nil { + projectQuotaSupported = true + } + quota := quota.Quota{} + if volume.config.Size > 0 || volume.config.Inodes > 0 { + if !projectQuotaSupported { + return nil, errors.New("Volume options size and inodes not supported. Filesystem does not support Project Quota") + } + quota.Size = volume.config.Size + quota.Inodes = volume.config.Inodes + } + if projectQuotaSupported { + if err := q.SetQuota(fullVolPath, quota); err != nil { + return nil, errors.Wrapf(err, "failed to set size quota size=%d inodes=%d for volume directory %q", volume.config.Size, volume.config.Inodes, fullVolPath) + } + } + volume.config.MountPoint = fullVolPath } diff --git a/libpod/util.go b/libpod/util.go index 7f4a01f28..3b32fb264 100644 --- a/libpod/util.go +++ b/libpod/util.go @@ -153,33 +153,22 @@ func queryPackageVersion(cmdArg ...string) string { return strings.Trim(output, "\n") } -func equeryVersion(path string) string { - return queryPackageVersion("/usr/bin/equery", "b", path) -} - -func pacmanVersion(path string) string { - return queryPackageVersion("/usr/bin/pacman", "-Qo", path) -} - -func dpkgVersion(path string) string { - return queryPackageVersion("/usr/bin/dpkg", "-S", path) -} - -func rpmVersion(path string) string { - return queryPackageVersion("/usr/bin/rpm", "-q", "-f", path) -} - -func packageVersion(program string) string { - if out := rpmVersion(program); out != unknownPackage { - return out - } - if out := dpkgVersion(program); out != unknownPackage { - return out +func packageVersion(program string) string { // program is full path + packagers := [][]string{ + {"/usr/bin/rpm", "-q", "-f"}, + {"/usr/bin/dpkg", "-S"}, // Debian, Ubuntu + {"/usr/bin/pacman", "-Qo"}, // Arch + {"/usr/bin/qfile", "-qv"}, // Gentoo (quick) + {"/usr/bin/equery", "b"}, // Gentoo (slow) } - if out := pacmanVersion(program); out != unknownPackage { - return out + + for _, cmd := range packagers { + cmd = append(cmd, program) + if out := queryPackageVersion(cmd...); out != unknownPackage { + return out + } } - return equeryVersion(program) + return unknownPackage } func programVersion(mountProgram string) (string, error) { diff --git a/libpod/volume.go b/libpod/volume.go index 506c45b5a..8f3dc4fcc 100644 --- a/libpod/volume.go +++ b/libpod/volume.go @@ -49,6 +49,10 @@ type VolumeConfig struct { UID int `json:"uid"` // GID the volume will be created as. GID int `json:"gid"` + // Size maximum of the volume. + Size uint64 `json:"size"` + // Inodes maximum of the volume. + Inodes uint64 `json:"inodes"` } // VolumeState holds the volume's mutable state. diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go index 19008a253..f69f1c044 100644 --- a/libpod/volume_internal.go +++ b/libpod/volume_internal.go @@ -49,6 +49,9 @@ func (v *Volume) needsMount() bool { if _, ok := v.config.Options["GID"]; ok { index++ } + if _, ok := v.config.Options["SIZE"]; ok { + index++ + } // when uid or gid is set there is also the "o" option // set so we have to ignore this one as well if index > 0 { diff --git a/libpod/volume_internal_linux.go b/libpod/volume_internal_linux.go index 92391de1d..45cd22385 100644 --- a/libpod/volume_internal_linux.go +++ b/libpod/volume_internal_linux.go @@ -104,7 +104,7 @@ func (v *Volume) mount() error { logrus.Debugf("Running mount command: %s %s", mountPath, strings.Join(mountArgs, " ")) if output, err := mountCmd.CombinedOutput(); err != nil { - logrus.Debugf("Mount failed with %v", err) + logrus.Debugf("Mount %v failed with %v", mountCmd, err) return errors.Wrapf(errors.Errorf(string(output)), "error mounting volume %s", v.Name()) } diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 2a0a0b725..95c09ff0e 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -403,22 +403,24 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON, state.Status = define.ContainerStateCreated.String() } - state.Health = &types.Health{ - Status: inspect.State.Healthcheck.Status, - FailingStreak: inspect.State.Healthcheck.FailingStreak, - } - - log := inspect.State.Healthcheck.Log + if l.HasHealthCheck() && state.Status != "created" { + state.Health = &types.Health{ + Status: inspect.State.Healthcheck.Status, + FailingStreak: inspect.State.Healthcheck.FailingStreak, + } - for _, item := range log { - res := &types.HealthcheckResult{} - s, _ := time.Parse(time.RFC3339Nano, item.Start) - e, _ := time.Parse(time.RFC3339Nano, item.End) - res.Start = s - res.End = e - res.ExitCode = item.ExitCode - res.Output = item.Output - state.Health.Log = append(state.Health.Log, res) + log := inspect.State.Healthcheck.Log + + for _, item := range log { + res := &types.HealthcheckResult{} + s, _ := time.Parse(time.RFC3339Nano, item.Start) + e, _ := time.Parse(time.RFC3339Nano, item.End) + res.Start = s + res.End = e + res.ExitCode = item.ExitCode + res.Output = item.Output + state.Health.Log = append(state.Health.Log, res) + } } formatCapabilities(inspect.HostConfig.CapDrop) @@ -495,6 +497,17 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON, exposedPorts[exposedPort] = struct{}{} } + var healthcheck *container.HealthConfig + if inspect.Config.Healthcheck != nil { + healthcheck = &container.HealthConfig{ + Test: inspect.Config.Healthcheck.Test, + Interval: inspect.Config.Healthcheck.Interval, + Timeout: inspect.Config.Healthcheck.Timeout, + StartPeriod: inspect.Config.Healthcheck.StartPeriod, + Retries: inspect.Config.Healthcheck.Retries, + } + } + config := container.Config{ Hostname: l.Hostname(), Domainname: inspect.Config.DomainName, @@ -508,7 +521,7 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON, StdinOnce: inspect.Config.StdinOnce, Env: inspect.Config.Env, Cmd: l.Command(), - Healthcheck: nil, + Healthcheck: healthcheck, ArgsEscaped: false, Image: imageName, Volumes: nil, diff --git a/pkg/api/handlers/libpod/containers_stats.go b/pkg/api/handlers/libpod/containers_stats.go index 75c404d4f..8a04884b0 100644 --- a/pkg/api/handlers/libpod/containers_stats.go +++ b/pkg/api/handlers/libpod/containers_stats.go @@ -3,28 +3,39 @@ package libpod import ( "encoding/json" "net/http" - "time" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers/utils" + "github.com/containers/podman/v3/pkg/cgroups" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/infra/abi" + "github.com/containers/podman/v3/pkg/rootless" "github.com/gorilla/schema" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) -const DefaultStatsPeriod = 5 * time.Second - func StatsContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) + // Check if service is running rootless (cheap check) + if rootless.IsRootless() { + // if so, then verify cgroup v2 available (more expensive check) + if isV2, _ := cgroups.IsCgroup2UnifiedMode(); !isV2 { + msg := "Container stats resource only available for cgroup v2" + utils.Error(w, msg, http.StatusConflict, errors.New(msg)) + return + } + } + query := struct { Containers []string `schema:"containers"` Stream bool `schema:"stream"` + Interval int `schema:"interval"` }{ - Stream: true, + Stream: true, + Interval: 5, } 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())) @@ -36,7 +47,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { containerEngine := abi.ContainerEngine{Libpod: runtime} statsOptions := entities.ContainerStatsOptions{ - Stream: query.Stream, + Stream: query.Stream, + Interval: query.Interval, } // Stats will stop if the connection is closed. diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 50e059ecc..0ec4f95d9 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -1085,6 +1085,8 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // description: no error // 404: // $ref: "#/responses/NoSuchContainer" + // 409: + // $ref: "#/responses/ConflictError" // 500: // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/containers/{name}/stats"), s.APIHandler(compat.StatsContainer)).Methods(http.MethodGet) @@ -1106,6 +1108,11 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // type: boolean // default: true // description: Stream the output + // - in: query + // name: interval + // type: integer + // default: 5 + // description: Time in seconds between stats reports // produces: // - application/json // responses: @@ -1113,6 +1120,8 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // description: no error // 404: // $ref: "#/responses/NoSuchContainer" + // 409: + // $ref: "#/responses/ConflictError" // 500: // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/containers/stats"), s.APIHandler(libpod.StatsContainer)).Methods(http.MethodGet) diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 86304f392..bc7b0c8c9 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -223,6 +223,9 @@ func Stats(ctx context.Context, containers []string, options *StatsOptions) (cha if err != nil { return nil, err } + if !response.IsSuccess() { + return nil, response.Process(nil) + } statsChan := make(chan entities.ContainerStatsReport) diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go index cf088441f..3e9a384de 100644 --- a/pkg/bindings/containers/types.go +++ b/pkg/bindings/containers/types.go @@ -166,7 +166,8 @@ type StartOptions struct { //go:generate go run ../generator/generator.go StatsOptions // StatsOptions are optional options for getting stats on containers type StatsOptions struct { - Stream *bool + Stream *bool + Interval *int } //go:generate go run ../generator/generator.go TopOptions diff --git a/pkg/bindings/containers/types_stats_options.go b/pkg/bindings/containers/types_stats_options.go index 8f6a03301..604004eb6 100644 --- a/pkg/bindings/containers/types_stats_options.go +++ b/pkg/bindings/containers/types_stats_options.go @@ -35,3 +35,19 @@ func (o *StatsOptions) GetStream() bool { } return *o.Stream } + +// WithInterval +func (o *StatsOptions) WithInterval(value int) *StatsOptions { + v := &value + o.Interval = v + return o +} + +// GetInterval +func (o *StatsOptions) GetInterval() int { + var interval int + if o.Interval == nil { + return interval + } + return *o.Interval +} diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 564921c52..d2a7505a8 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -440,6 +440,8 @@ type ContainerStatsOptions struct { Latest bool // Stream stats. Stream bool + // Interval in seconds + Interval int } // ContainerStatsReport is used for streaming container stats. diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 2003879b8..ddd768328 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -1283,6 +1283,9 @@ func (ic *ContainerEngine) Shutdown(_ context.Context) { } func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) (statsChan chan entities.ContainerStatsReport, err error) { + if options.Interval < 1 { + return nil, errors.New("Invalid interval, must be a positive number greater zero") + } statsChan = make(chan entities.ContainerStatsReport, 1) containerFunc := ic.Libpod.GetRunningContainers @@ -1363,7 +1366,7 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri return } - time.Sleep(time.Second) + time.Sleep(time.Second * time.Duration(options.Interval)) goto stream }() diff --git a/pkg/domain/infra/abi/parse/parse.go b/pkg/domain/infra/abi/parse/parse.go index 56c747711..5a75e1216 100644 --- a/pkg/domain/infra/abi/parse/parse.go +++ b/pkg/domain/infra/abi/parse/parse.go @@ -6,12 +6,13 @@ import ( "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" + units "github.com/docker/go-units" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) // Handle volume options from CLI. -// Parse "o" option to find UID, GID. +// Parse "o" option to find UID, GID, Size. func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) { libpodOptions := []libpod.VolumeCreateOption{} volumeOptions := make(map[string]string) @@ -28,6 +29,24 @@ func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) // "opt=value" splitO := strings.SplitN(o, "=", 2) switch strings.ToLower(splitO[0]) { + case "size": + size, err := units.FromHumanSize(splitO[1]) + if err != nil { + return nil, errors.Wrapf(err, "cannot convert size %s to integer", splitO[1]) + } + libpodOptions = append(libpodOptions, libpod.WithVolumeSize(uint64(size))) + finalVal = append(finalVal, o) + // set option "SIZE": "$size" + volumeOptions["SIZE"] = splitO[1] + case "inodes": + inodes, err := strconv.ParseUint(splitO[1], 10, 64) + if err != nil { + return nil, errors.Wrapf(err, "cannot convert inodes %s to integer", splitO[1]) + } + libpodOptions = append(libpodOptions, libpod.WithVolumeInodes(uint64(inodes))) + finalVal = append(finalVal, o) + // set option "INODES": "$size" + volumeOptions["INODES"] = splitO[1] case "uid": if len(splitO) != 2 { return nil, errors.Wrapf(define.ErrInvalidArg, "uid option must provide a UID") diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 58f9c5fb0..3c2802165 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -873,7 +873,7 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri if options.Latest { return nil, errors.New("latest is not supported for the remote client") } - return containers.Stats(ic.ClientCtx, namesOrIds, new(containers.StatsOptions).WithStream(options.Stream)) + return containers.Stats(ic.ClientCtx, namesOrIds, new(containers.StatsOptions).WithStream(options.Stream).WithInterval(options.Interval)) } // ShouldRestart reports back whether the container will restart diff --git a/pkg/rootlessport/rootlessport_linux.go b/pkg/rootlessport/rootlessport_linux.go index 7cb54a7c3..ede216bfe 100644 --- a/pkg/rootlessport/rootlessport_linux.go +++ b/pkg/rootlessport/rootlessport_linux.go @@ -17,9 +17,11 @@ import ( "fmt" "io" "io/ioutil" + "net" "os" "os/exec" "os/signal" + "path/filepath" "github.com/containernetworking/plugins/pkg/ns" "github.com/containers/storage/pkg/reexec" @@ -43,12 +45,14 @@ const ( // Config needs to be provided to the process via stdin as a JSON string. // stdin needs to be closed after the message has been written. type Config struct { - Mappings []ocicni.PortMapping - NetNSPath string - ExitFD int - ReadyFD int - TmpDir string - ChildIP string + Mappings []ocicni.PortMapping + NetNSPath string + ExitFD int + ReadyFD int + TmpDir string + ChildIP string + ContainerID string + RootlessCNI bool } func init() { @@ -126,6 +130,12 @@ func parent() error { } }() + socketDir := filepath.Join(cfg.TmpDir, "rp") + err = os.MkdirAll(socketDir, 0700) + if err != nil { + return err + } + // create the parent driver stateDir, err := ioutil.TempDir(cfg.TmpDir, "rootlessport") if err != nil { @@ -231,6 +241,16 @@ outer: return err } + // we only need to have a socket to reload ports when we run under rootless cni + if cfg.RootlessCNI { + socket, err := net.Listen("unix", filepath.Join(socketDir, cfg.ContainerID)) + if err != nil { + return err + } + defer socket.Close() + go serve(socket, driver) + } + // write and close ReadyFD (convention is same as slirp4netns --ready-fd) logrus.Info("ready") if _, err := readyW.Write([]byte("1")); err != nil { @@ -248,6 +268,53 @@ outer: return nil } +func serve(listener net.Listener, pm rkport.Manager) { + for { + conn, err := listener.Accept() + if err != nil { + // we cannot log this error, stderr is already closed + continue + } + ctx := context.TODO() + err = handler(ctx, conn, pm) + if err != nil { + conn.Write([]byte(err.Error())) + } else { + conn.Write([]byte("OK")) + } + conn.Close() + } +} + +func handler(ctx context.Context, conn io.Reader, pm rkport.Manager) error { + var childIP string + dec := json.NewDecoder(conn) + err := dec.Decode(&childIP) + if err != nil { + return errors.Wrap(err, "rootless port failed to decode ports") + } + portStatus, err := pm.ListPorts(ctx) + if err != nil { + return errors.Wrap(err, "rootless port failed to list ports") + } + for _, status := range portStatus { + err = pm.RemovePort(ctx, status.ID) + if err != nil { + return errors.Wrap(err, "rootless port failed to remove port") + } + } + // add the ports with the new child IP + for _, status := range portStatus { + // set the new child IP + status.Spec.ChildIP = childIP + _, err = pm.AddPort(ctx, status.Spec) + if err != nil { + return errors.Wrap(err, "rootless port failed to add port") + } + } + return nil +} + func exposePorts(pm rkport.Manager, portMappings []ocicni.PortMapping, childIP string) error { ctx := context.TODO() for _, i := range portMappings { diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index c5b2f5ec1..610d3e36d 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -65,6 +65,16 @@ t GET libpod/containers/json?last=1 200 \ cid=$(jq -r '.[0].Id' <<<"$output") +if root; then + t GET libpod/containers/stats?containers='[$cid]' 200 +else + if have_cgroupsv2; then + t GET libpod/containers/stats?containers='[$cid]' 200 + else + t GET libpod/containers/stats?containers='[$cid]' 409 + fi +fi + t DELETE libpod/containers/$cid 204 # Issue #6799: it should be possible to start a container, even w/o args. diff --git a/test/apiv2/python/rest_api/test_v2_0_0_container.py b/test/apiv2/python/rest_api/test_v2_0_0_container.py index 30d902d8c..dbad6824f 100644 --- a/test/apiv2/python/rest_api/test_v2_0_0_container.py +++ b/test/apiv2/python/rest_api/test_v2_0_0_container.py @@ -36,26 +36,48 @@ class ContainerTestCase(APITestCase): r = requests.post( self.podman_url + "/v1.40/containers/create?name=topcontainer", - json={"Healthcheck": {"Test": ["CMD-SHELL", "exit 0"], "Interval":1000, "Timeout":1000, "Retries": 5}, "Cmd": ["top"], "Image": "alpine:latest"}, + json={"Cmd": ["top"], + "Image": "alpine:latest", + "Healthcheck": { + "Test": ["CMD", "pidof", "top"], + "Interval": 5000000000, + "Timeout": 2000000000, + "Retries": 3, + "StartPeriod": 5000000000 + } + }, ) self.assertEqual(r.status_code, 201, r.text) payload = r.json() container_id = payload["Id"] self.assertIsNotNone(container_id) - r = requests.get(self.podman_url + f"/v1.40/containers/{payload['Id']}/json") + r = requests.get(self.podman_url + f"/v1.40/containers/{container_id}/json") self.assertEqual(r.status_code, 200, r.text) self.assertId(r.content) out = r.json() - state = out["State"]["Health"] - self.assertIsInstance(state, dict) - - r = requests.get(self.uri(f"/containers/{payload['Id']}/json")) + self.assertIsNone(out["State"].get("Health")) + self.assertListEqual(["CMD", "pidof", "top"], out["Config"]["Healthcheck"]["Test"]) + self.assertEqual(5000000000, out["Config"]["Healthcheck"]["Interval"]) + self.assertEqual(2000000000, out["Config"]["Healthcheck"]["Timeout"]) + self.assertEqual(3, out["Config"]["Healthcheck"]["Retries"]) + self.assertEqual(5000000000, out["Config"]["Healthcheck"]["StartPeriod"]) + + r = requests.get(self.uri(f"/containers/{container_id}/json")) self.assertEqual(r.status_code, 200, r.text) self.assertId(r.content) out = r.json() hc = out["Config"]["Healthcheck"]["Test"] - self.assertListEqual(["CMD-SHELL", "exit 0"], hc) + self.assertListEqual(["CMD", "pidof", "top"], hc) + + r = requests.post(self.podman_url + f"/v1.40/containers/{container_id}/start") + self.assertEqual(r.status_code, 204, r.text) + + r = requests.get(self.podman_url + f"/v1.40/containers/{container_id}/json") + self.assertEqual(r.status_code, 200, r.text) + out = r.json() + state = out["State"]["Health"] + self.assertIsInstance(state, dict) def test_stats(self): r = requests.get(self.uri(self.resolve_container("/containers/{}/stats?stream=false"))) diff --git a/test/e2e/pod_stats_test.go b/test/e2e/pod_stats_test.go index 46043b16d..5ec209034 100644 --- a/test/e2e/pod_stats_test.go +++ b/test/e2e/pod_stats_test.go @@ -37,19 +37,19 @@ var _ = Describe("Podman pod stats", func() { processTestResult(f) }) - It("podman stats should run with no pods", func() { + It("podman pod stats should run with no pods", func() { session := podmanTest.Podman([]string{"pod", "stats", "--no-stream"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) }) - It("podman stats with a bogus pod", func() { + It("podman pod stats with a bogus pod", func() { session := podmanTest.Podman([]string{"pod", "stats", "foobar"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(125)) }) - It("podman stats on a specific running pod", func() { + It("podman pod stats on a specific running pod", func() { _, ec, podid := podmanTest.CreatePod(nil) Expect(ec).To(Equal(0)) @@ -66,7 +66,7 @@ var _ = Describe("Podman pod stats", func() { Expect(stats).Should(Exit(0)) }) - It("podman stats on a specific running pod with shortID", func() { + It("podman pod stats on a specific running pod with shortID", func() { _, ec, podid := podmanTest.CreatePod(nil) Expect(ec).To(Equal(0)) @@ -83,7 +83,7 @@ var _ = Describe("Podman pod stats", func() { Expect(stats).Should(Exit(0)) }) - It("podman stats on a specific running pod with name", func() { + It("podman pod stats on a specific running pod with name", func() { _, ec, podid := podmanTest.CreatePod(map[string][]string{"--name": {"test"}}) Expect(ec).To(Equal(0)) @@ -100,7 +100,7 @@ var _ = Describe("Podman pod stats", func() { Expect(stats).Should(Exit(0)) }) - It("podman stats on running pods", func() { + It("podman pod stats on running pods", func() { _, ec, podid := podmanTest.CreatePod(nil) Expect(ec).To(Equal(0)) @@ -117,7 +117,7 @@ var _ = Describe("Podman pod stats", func() { Expect(stats).Should(Exit(0)) }) - It("podman stats on all pods", func() { + It("podman pod stats on all pods", func() { _, ec, podid := podmanTest.CreatePod(nil) Expect(ec).To(Equal(0)) @@ -134,7 +134,7 @@ var _ = Describe("Podman pod stats", func() { Expect(stats).Should(Exit(0)) }) - It("podman stats with json output", func() { + It("podman pod stats with json output", func() { _, ec, podid := podmanTest.CreatePod(nil) Expect(ec).To(Equal(0)) @@ -151,7 +151,7 @@ var _ = Describe("Podman pod stats", func() { Expect(stats).Should(Exit(0)) Expect(stats.IsJSONOutputValid()).To(BeTrue()) }) - It("podman stats with GO template", func() { + It("podman pod stats with GO template", func() { _, ec, podid := podmanTest.CreatePod(nil) Expect(ec).To(Equal(0)) @@ -163,7 +163,7 @@ var _ = Describe("Podman pod stats", func() { Expect(stats).To(Exit(0)) }) - It("podman stats with invalid GO template", func() { + It("podman pod stats with invalid GO template", func() { _, ec, podid := podmanTest.CreatePod(nil) Expect(ec).To(Equal(0)) @@ -175,7 +175,7 @@ var _ = Describe("Podman pod stats", func() { Expect(stats).To(ExitWithError()) }) - It("podman stats on net=host post", func() { + It("podman pod stats on net=host post", func() { SkipIfRootless("--net=host not supported for rootless pods at present") podName := "testPod" podCreate := podmanTest.Podman([]string{"pod", "create", "--net=host", "--name", podName}) diff --git a/test/e2e/run_cgroup_parent_test.go b/test/e2e/run_cgroup_parent_test.go index 300c3a8e0..3e261961b 100644 --- a/test/e2e/run_cgroup_parent_test.go +++ b/test/e2e/run_cgroup_parent_test.go @@ -2,6 +2,7 @@ package integration import ( "fmt" + "io/ioutil" "os" "path/filepath" "strings" @@ -80,7 +81,21 @@ var _ = Describe("Podman run with --cgroup-parent", func() { exec.WaitWithDefaultTimeout() Expect(exec).Should(Exit(0)) - cgroup := filepath.Dir(strings.TrimRight(strings.Replace(exec.OutputToString(), "0::", "", -1), "\n")) + containerCgroup := strings.TrimRight(strings.Replace(exec.OutputToString(), "0::", "", -1), "\n") + + content, err := ioutil.ReadFile(filepath.Join("/sys/fs/cgroup", containerCgroup, "cgroup.procs")) + Expect(err).To(BeNil()) + + // Move the container process to a sub cgroup + subCgroupPath := filepath.Join(filepath.Join("/sys/fs/cgroup", containerCgroup, "old-container")) + + err = os.MkdirAll(subCgroupPath, 0755) + Expect(err).To(BeNil()) + + err = ioutil.WriteFile(filepath.Join(subCgroupPath, "cgroup.procs"), content, 0644) + Expect(err).To(BeNil()) + + cgroup := filepath.Dir(containerCgroup) run = podmanTest.Podman([]string{"--cgroup-manager=cgroupfs", "run", "-d", fmt.Sprintf("--cgroup-parent=%s", cgroup), fedoraMinimal, "sleep", "100"}) run.WaitWithDefaultTimeout() diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 80a82ea05..92388b099 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -685,13 +685,6 @@ var _ = Describe("Podman run networking", func() { Expect(podrm).Should(Exit(0)) }) - It("podman run net=host adds entry to /etc/hosts", func() { - run := podmanTest.Podman([]string{"run", "--net=host", ALPINE, "cat", "/etc/hosts"}) - run.WaitWithDefaultTimeout() - Expect(run).Should(Exit(0)) - Expect(strings.Contains(run.OutputToString(), "127.0.1.1")).To(BeTrue()) - }) - It("podman run with --net=host and --hostname sets correct hostname", func() { hostname := "testctr" run := podmanTest.Podman([]string{"run", "--net=host", "--hostname", hostname, ALPINE, "hostname"}) @@ -731,10 +724,6 @@ var _ = Describe("Podman run networking", func() { ping_test("--net=none") }) - It("podman attempt to ping container name and hostname --net=host", func() { - ping_test("--net=host") - }) - It("podman attempt to ping container name and hostname --net=private", func() { ping_test("--net=private") }) diff --git a/test/e2e/stats_test.go b/test/e2e/stats_test.go index e32d515a0..a0be5d462 100644 --- a/test/e2e/stats_test.go +++ b/test/e2e/stats_test.go @@ -1,5 +1,3 @@ -// +build - package integration import ( @@ -84,15 +82,49 @@ var _ = Describe("Podman stats", func() { Expect(session).Should(Exit(0)) }) - It("podman stats only output CPU data", func() { + It("podman stats with GO template", func() { session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - session = podmanTest.Podman([]string{"stats", "--all", "--no-stream", "--format", "\"{{.ID}} {{.UpTime}} {{.AVGCPU}}\""}) + stats := podmanTest.Podman([]string{"stats", "-a", "--no-reset", "--no-stream", "--format", "table {{.ID}} {{.AVGCPU}} {{.MemUsage}} {{.CPU}} {{.NetIO}} {{.BlockIO}} {{.PIDS}}"}) + stats.WaitWithDefaultTimeout() + Expect(stats).To(Exit(0)) + }) + + It("podman stats with invalid GO template", func() { + session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() - Expect(session.LineInOutputContains("UpTime")).To(BeTrue()) - Expect(session.LineInOutputContains("AVGCPU")).To(BeTrue()) Expect(session).Should(Exit(0)) + stats := podmanTest.Podman([]string{"stats", "-a", "--no-reset", "--no-stream", "--format", "\"table {{.ID}} {{.NoSuchField}} \""}) + stats.WaitWithDefaultTimeout() + Expect(stats).To(ExitWithError()) + }) + + It("podman stats with negative interval", func() { + session := podmanTest.RunTopContainer("") + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + stats := podmanTest.Podman([]string{"stats", "-a", "--no-reset", "--no-stream", "--interval=-1"}) + stats.WaitWithDefaultTimeout() + Expect(stats).To(ExitWithError()) + }) + + It("podman stats with zero interval", func() { + session := podmanTest.RunTopContainer("") + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + stats := podmanTest.Podman([]string{"stats", "-a", "--no-reset", "--no-stream", "--interval=0"}) + stats.WaitWithDefaultTimeout() + Expect(stats).To(ExitWithError()) + }) + + It("podman stats with interval", func() { + session := podmanTest.RunTopContainer("") + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + stats := podmanTest.Podman([]string{"stats", "-a", "--no-reset", "--no-stream", "--interval=5"}) + stats.WaitWithDefaultTimeout() + Expect(stats).Should(Exit(0)) }) It("podman stats with json output", func() { diff --git a/test/system/005-info.bats b/test/system/005-info.bats index 4b419841e..50c3ceb30 100644 --- a/test/system/005-info.bats +++ b/test/system/005-info.bats @@ -33,12 +33,16 @@ cgroupVersion: v[12] expr_nvr="[a-z0-9-]\\\+-[a-z0-9.]\\\+-[a-z0-9]\\\+\." expr_path="/[a-z0-9\\\/.-]\\\+\\\$" + # FIXME: if we're ever able to get package versions on Debian, + # add '-[0-9]' to all '*.package' queries below. tests=" host.buildahVersion | [0-9.] host.conmon.path | $expr_path +host.conmon.package | .*conmon.* host.cgroupManager | \\\(systemd\\\|cgroupfs\\\) host.cgroupVersion | v[12] host.ociRuntime.path | $expr_path +host.ociRuntime.package | .*\\\(crun\\\|runc\\\).* store.configFile | $expr_path store.graphDriverName | [a-z0-9]\\\+\\\$ store.graphRoot | $expr_path diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index 495c7948b..6ffee7eaf 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -390,4 +390,89 @@ load helpers run_podman network rm -f $netname } +# Test for https://github.com/containers/podman/issues/10052 +@test "podman network connect/disconnect with port forwarding" { + random_1=$(random_string 30) + HOST_PORT=12345 + SERVER=http://127.0.0.1:$HOST_PORT + + # Create a test file with random content + INDEX1=$PODMAN_TMPDIR/hello.txt + echo $random_1 > $INDEX1 + + local netname=testnet-$(random_string 10) + run_podman network create $netname + is "$output" ".*/cni/net.d/$netname.conflist" "output of 'network create'" + + local netname2=testnet2-$(random_string 10) + run_podman network create $netname2 + is "$output" ".*/cni/net.d/$netname2.conflist" "output of 'network create'" + + # First, run a container in background to ensure that the rootless cni ns + # is not destroyed after network disconnect. + run_podman run -d --network $netname $IMAGE top + background_cid=$output + + # Run a httpd container on first network with exposed port + run_podman run -d -p "$HOST_PORT:80" \ + --network $netname \ + -v $INDEX1:/var/www/index.txt:Z \ + -w /var/www \ + $IMAGE /bin/busybox-extras httpd -f -p 80 + cid=$output + + # Verify http contents: curl from localhost + run curl --max-time 3 -s $SERVER/index.txt + is "$output" "$random_1" "curl 127.0.0.1:/index.txt" + + run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").IPAddress}}" + ip="$output" + run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").MacAddress}}" + mac="$output" + + run_podman network disconnect $netname $cid + + # check that we cannot curl (timeout after 3 sec) + run curl --max-time 3 -s $SERVER/index.txt + if [ "$status" -eq 0 ]; then + die "curl did not fail, it should have timed out or failed with non zero exit code" + fi + + run_podman network connect $netname $cid + + # curl should work again + run curl --max-time 3 -s $SERVER/index.txt + is "$output" "$random_1" "curl 127.0.0.1:/index.txt should work again" + + # check that we have a new ip and mac + # if the ip is still the same this whole test turns into a nop + run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").IPAddress}}" + if [[ "$output" == "$ip" ]]; then + die "IP address did not change after podman network disconnect/connect" + fi + run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").MacAddress}}" + if [[ "$output" == "$mac" ]]; then + die "MAC address did not change after podman network disconnect/connect" + fi + + # connect a second network + run_podman network connect $netname2 $cid + + # curl should work + run curl --max-time 3 -s $SERVER/index.txt + is "$output" "$random_1" "curl 127.0.0.1:/index.txt should work" + + # disconnect the first network + run_podman network disconnect $netname $cid + + # curl should still work + run curl --max-time 3 -s $SERVER/index.txt + is "$output" "$random_1" "curl 127.0.0.1:/index.txt should still work" + + # cleanup + run_podman stop -t 0 $cid $background_cid + run_podman rm -f $cid $background_cid + run_podman network rm -f $netname $netname2 +} + # vim: filetype=sh diff --git a/vendor/github.com/docker/docker/api/swagger.yaml b/vendor/github.com/docker/docker/api/swagger.yaml index 1294e5a22..bada4a8e3 100644 --- a/vendor/github.com/docker/docker/api/swagger.yaml +++ b/vendor/github.com/docker/docker/api/swagger.yaml @@ -5583,12 +5583,12 @@ paths: schema: $ref: "#/definitions/ErrorResponse" 404: - description: "no such container" + description: "no such image" schema: $ref: "#/definitions/ErrorResponse" examples: application/json: - message: "No such container: c2ada9df5af8" + message: "No such image: c2ada9df5af8" 409: description: "conflict" schema: diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go index b3d142d8c..14e1e38c2 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go @@ -1,12 +1,13 @@ package label import ( + "errors" + "fmt" "os" "os/user" "strings" "github.com/opencontainers/selinux/go-selinux" - "github.com/pkg/errors" ) // Valid Label Options @@ -53,11 +54,11 @@ func InitLabels(options []string) (plabel string, mlabel string, retErr error) { return "", selinux.PrivContainerMountLabel(), nil } if i := strings.Index(opt, ":"); i == -1 { - return "", "", errors.Errorf("Bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt) + return "", "", fmt.Errorf("Bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt) } con := strings.SplitN(opt, ":", 2) if !validOptions[con[0]] { - return "", "", errors.Errorf("Bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0]) + return "", "", fmt.Errorf("Bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0]) } if con[0] == "filetype" { mcon["type"] = con[1] @@ -151,7 +152,7 @@ func Relabel(path string, fileLabel string, shared bool) error { path = strings.TrimSuffix(path, "/") } if exclude_paths[path] { - return errors.Errorf("SELinux relabeling of %s is not allowed", path) + return fmt.Errorf("SELinux relabeling of %s is not allowed", path) } if shared { diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go index b336ebad3..9ffd77afa 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go @@ -1,7 +1,7 @@ package selinux import ( - "github.com/pkg/errors" + "errors" ) const ( diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go index a91a116f8..62df82a34 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go @@ -5,6 +5,7 @@ import ( "bytes" "crypto/rand" "encoding/binary" + "errors" "fmt" "io" "io/ioutil" @@ -18,7 +19,6 @@ import ( "github.com/bits-and-blooms/bitset" "github.com/opencontainers/selinux/pkg/pwalk" - "github.com/pkg/errors" "golang.org/x/sys/unix" ) @@ -120,7 +120,7 @@ func verifySELinuxfsMount(mnt string) bool { if err == nil { break } - if err == unix.EAGAIN || err == unix.EINTR { + if err == unix.EAGAIN || err == unix.EINTR { //nolint:errorlint // unix errors are bare continue } return false @@ -250,12 +250,12 @@ func isProcHandle(fh *os.File) error { if err == nil { break } - if err != unix.EINTR { - return errors.Wrapf(err, "statfs(%q) failed", fh.Name()) + if err != unix.EINTR { //nolint:errorlint // unix errors are bare + return &os.PathError{Op: "fstatfs", Path: fh.Name(), Err: err} } } if buf.Type != unix.PROC_SUPER_MAGIC { - return errors.Errorf("file %q is not on procfs", fh.Name()) + return fmt.Errorf("file %q is not on procfs", fh.Name()) } return nil @@ -311,8 +311,8 @@ func setFileLabel(fpath string, label string) error { if err == nil { break } - if err != unix.EINTR { - return errors.Wrapf(err, "failed to set file label on %s", fpath) + if err != unix.EINTR { //nolint:errorlint // unix errors are bare + return &os.PathError{Op: "lsetxattr", Path: fpath, Err: err} } } @@ -327,7 +327,7 @@ func fileLabel(fpath string) (string, error) { label, err := lgetxattr(fpath, xattrNameSelinux) if err != nil { - return "", err + return "", &os.PathError{Op: "lgetxattr", Path: fpath, Err: err} } // Trim the NUL byte at the end of the byte buffer, if present. if len(label) > 0 && label[len(label)-1] == '\x00' { @@ -390,7 +390,7 @@ func writeCon(fpath, val string) error { _, err = out.Write(nil) } if err != nil { - return errors.Wrapf(err, "failed to set %s on procfs", fpath) + return &os.PathError{Op: "write", Path: fpath, Err: err} } return nil } @@ -489,13 +489,13 @@ func (l *level) parseLevel(levelStr string) error { lvl := strings.SplitN(levelStr, ":", 2) sens, err := parseLevelItem(lvl[0], sensitivity) if err != nil { - return errors.Wrap(err, "failed to parse sensitivity") + return fmt.Errorf("failed to parse sensitivity: %w", err) } l.sens = sens if len(lvl) > 1 { cats, err := catsToBitset(lvl[1]) if err != nil { - return errors.Wrap(err, "failed to parse categories") + return fmt.Errorf("failed to parse categories: %w", err) } l.cats = cats } @@ -513,14 +513,14 @@ func rangeStrToMLSRange(rangeStr string) (*mlsRange, error) { case 2: mlsRange.high = &level{} if err := mlsRange.high.parseLevel(levelSlice[1]); err != nil { - return nil, errors.Wrapf(err, "failed to parse high level %q", levelSlice[1]) + return nil, fmt.Errorf("failed to parse high level %q: %w", levelSlice[1], err) } fallthrough // rangeStr that is single level, e.g. s6:c0,c3,c5,c30.c1023 case 1: mlsRange.low = &level{} if err := mlsRange.low.parseLevel(levelSlice[0]); err != nil { - return nil, errors.Wrapf(err, "failed to parse low level %q", levelSlice[0]) + return nil, fmt.Errorf("failed to parse low level %q: %w", levelSlice[0], err) } } @@ -697,17 +697,21 @@ func socketLabel() (string, error) { // peerLabel retrieves the label of the client on the other side of a socket func peerLabel(fd uintptr) (string, error) { - return unix.GetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_PEERSEC) + label, err := unix.GetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_PEERSEC) + if err != nil { + return "", &os.PathError{Op: "getsockopt", Path: "fd " + strconv.Itoa(int(fd)), Err: err} + } + return label, nil } // setKeyLabel takes a process label and tells the kernel to assign the // label to the next kernel keyring that gets created func setKeyLabel(label string) error { err := writeCon("/proc/self/attr/keycreate", label) - if os.IsNotExist(errors.Cause(err)) { + if errors.Is(err, os.ErrNotExist) { return nil } - if label == "" && os.IsPermission(errors.Cause(err)) { + if label == "" && errors.Is(err, os.ErrPermission) { return nil } return err @@ -784,7 +788,7 @@ func enforceMode() int { // setEnforceMode sets the current SELinux mode Enforcing, Permissive. // Disabled is not valid, since this needs to be set at boot time. func setEnforceMode(mode int) error { - return ioutil.WriteFile(selinuxEnforcePath(), []byte(strconv.Itoa(mode)), 0644) + return ioutil.WriteFile(selinuxEnforcePath(), []byte(strconv.Itoa(mode)), 0o644) } // defaultEnforceMode returns the systems default SELinux mode Enforcing, @@ -985,7 +989,7 @@ func addMcs(processLabel, fileLabel string) (string, string) { // securityCheckContext validates that the SELinux label is understood by the kernel func securityCheckContext(val string) error { - return ioutil.WriteFile(path.Join(getSelinuxMountPoint(), "context"), []byte(val), 0644) + return ioutil.WriteFile(path.Join(getSelinuxMountPoint(), "context"), []byte(val), 0o644) } // copyLevel returns a label with the MLS/MCS level from src label replaced on @@ -1023,7 +1027,7 @@ func badPrefix(fpath string) error { badPrefixes := []string{"/usr"} for _, prefix := range badPrefixes { if strings.HasPrefix(fpath, prefix) { - return errors.Errorf("relabeling content in %s is not allowed", prefix) + return fmt.Errorf("relabeling content in %s is not allowed", prefix) } } return nil @@ -1050,7 +1054,7 @@ func chcon(fpath string, label string, recurse bool) error { return pwalk.Walk(fpath, func(p string, info os.FileInfo, err error) error { e := SetFileLabel(p, label) // Walk a file tree can race with removal, so ignore ENOENT - if os.IsNotExist(errors.Cause(e)) { + if errors.Is(e, os.ErrNotExist) { return nil } return e @@ -1072,7 +1076,8 @@ func dupSecOpt(src string) ([]string, error) { con["type"] == "" { return nil, nil } - dup := []string{"user:" + con["user"], + dup := []string{ + "user:" + con["user"], "role:" + con["role"], "type:" + con["type"], } @@ -1140,9 +1145,8 @@ func findUserInContext(context Context, r io.Reader, verifier func(string) error return outConn, nil } } - if err := scanner.Err(); err != nil { - return "", errors.Wrap(err, "failed to scan for context") + return "", fmt.Errorf("failed to scan for context: %w", err) } return "", nil @@ -1155,7 +1159,7 @@ func getDefaultContextFromReaders(c *defaultSECtx) (string, error) { context, err := newContext(c.scon) if err != nil { - return "", errors.Wrapf(err, "failed to create label for %s", c.scon) + return "", fmt.Errorf("failed to create label for %s: %w", c.scon, err) } // set so the verifier validates the matched context with the provided user and level. @@ -1180,7 +1184,7 @@ func getDefaultContextFromReaders(c *defaultSECtx) (string, error) { return conn, nil } - return "", errors.Wrapf(ErrContextMissing, "context not found: %q", c.scon) + return "", fmt.Errorf("context %q not found: %w", c.scon, ErrContextMissing) } func getDefaultContextWithLevel(user, level, scon string) (string, error) { diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/xattrs_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/xattrs_linux.go index 117c255be..c6b0a7f26 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/xattrs_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/xattrs_linux.go @@ -10,7 +10,7 @@ func lgetxattr(path, attr string) ([]byte, error) { // Start with a 128 length byte array dest := make([]byte, 128) sz, errno := doLgetxattr(path, attr, dest) - for errno == unix.ERANGE { + for errno == unix.ERANGE { //nolint:errorlint // unix errors are bare // Buffer too small, use zero-sized buffer to get the actual size sz, errno = doLgetxattr(path, attr, []byte{}) if errno != nil { @@ -31,7 +31,7 @@ func lgetxattr(path, attr string) ([]byte, error) { func doLgetxattr(path, attr string, dest []byte) (int, error) { for { sz, err := unix.Lgetxattr(path, attr, dest) - if err != unix.EINTR { + if err != unix.EINTR { //nolint:errorlint // unix errors are bare return sz, err } } diff --git a/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go b/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go index 437b12b3e..a8088a196 100644 --- a/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go +++ b/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go @@ -1,12 +1,11 @@ package pwalk import ( + "fmt" "os" "path/filepath" "runtime" "sync" - - "github.com/pkg/errors" ) type WalkFunc = filepath.WalkFunc @@ -42,7 +41,7 @@ func Walk(root string, walkFn WalkFunc) error { func WalkN(root string, walkFn WalkFunc, num int) error { // make sure limit is sensible if num < 1 { - return errors.Errorf("walk(%q): num must be > 0", root) + return fmt.Errorf("walk(%q): num must be > 0", root) } files := make(chan *walkArgs, 2*num) diff --git a/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/parent.go b/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/parent.go index 2895a8f07..c6eecc826 100644 --- a/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/parent.go +++ b/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/parent.go @@ -12,6 +12,7 @@ import ( "strings" "sync" "syscall" + "time" "github.com/pkg/errors" @@ -41,7 +42,7 @@ func NewDriver(logWriter io.Writer, stateDir string) (port.ParentDriver, error) socketPath: socketPath, childReadyPipePath: childReadyPipePath, ports: make(map[int]*port.Status, 0), - stoppers: make(map[int]func() error, 0), + stoppers: make(map[int]func(context.Context) error, 0), nextID: 1, } return &d, nil @@ -53,7 +54,7 @@ type driver struct { childReadyPipePath string mu sync.Mutex ports map[int]*port.Status - stoppers map[int]func() error + stoppers map[int]func(context.Context) error nextID int } @@ -138,16 +139,27 @@ func (d *driver) AddPort(ctx context.Context, spec port.Spec) (*port.Status, err if err != nil { return nil, err } + // NOTE: routineStopCh is close-only channel. Do not send any data. + // See commit 4803f18fae1e39d200d98f09e445a97ccd6f5526 `Revert "port/builtin: RemovePort() block until conn is closed"` routineStopCh := make(chan struct{}) - routineStop := func() error { + routineStoppedCh := make(chan error) + routineStop := func(ctx context.Context) error { close(routineStopCh) - return nil // FIXME + select { + case stoppedResult, stoppedResultOk := <-routineStoppedCh: + if stoppedResultOk { + return stoppedResult + } + return errors.New("routineStoppedCh was closed without sending data?") + case <-ctx.Done(): + return errors.Wrap(err, "timed out while waiting for routineStoppedCh after closing routineStopCh") + } } switch spec.Proto { case "tcp", "tcp4", "tcp6": - err = tcp.Run(d.socketPath, spec, routineStopCh, d.logWriter) + err = tcp.Run(d.socketPath, spec, routineStopCh, routineStoppedCh, d.logWriter) case "udp", "udp4", "udp6": - err = udp.Run(d.socketPath, spec, routineStopCh, d.logWriter) + err = udp.Run(d.socketPath, spec, routineStopCh, routineStoppedCh, d.logWriter) default: // NOTREACHED return nil, errors.New("spec was not validated?") @@ -188,7 +200,12 @@ func (d *driver) RemovePort(ctx context.Context, id int) error { if !ok { return errors.Errorf("unknown id: %d", id) } - err := stop() + if _, ok := ctx.Deadline(); !ok { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, 5*time.Second) + defer cancel() + } + err := stop(ctx) delete(d.stoppers, id) delete(d.ports, id) return err diff --git a/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/tcp/tcp.go b/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/tcp/tcp.go index 7a7a167f1..32c714468 100644 --- a/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/tcp/tcp.go +++ b/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/tcp/tcp.go @@ -12,7 +12,7 @@ import ( "github.com/rootless-containers/rootlesskit/pkg/port/builtin/msg" ) -func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, logWriter io.Writer) error { +func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, stoppedCh chan error, logWriter io.Writer) error { ln, err := net.Listen(spec.Proto, net.JoinHostPort(spec.ParentIP, strconv.Itoa(spec.ParentPort))) if err != nil { fmt.Fprintf(logWriter, "listen: %v\n", err) @@ -31,7 +31,10 @@ func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, logWriter io } }() go func() { - defer ln.Close() + defer func() { + stoppedCh <- ln.Close() + close(stoppedCh) + }() for { select { case c, ok := <-newConns: diff --git a/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp/udp.go b/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp/udp.go index 0080dd22c..67062117a 100644 --- a/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp/udp.go +++ b/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp/udp.go @@ -13,7 +13,7 @@ import ( "github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp/udpproxy" ) -func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, logWriter io.Writer) error { +func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, stoppedCh chan error, logWriter io.Writer) error { addr, err := net.ResolveUDPAddr(spec.Proto, net.JoinHostPort(spec.ParentIP, strconv.Itoa(spec.ParentPort))) if err != nil { return err @@ -51,6 +51,8 @@ func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, logWriter io case <-stopCh: // udpp.Close closes ln as well udpp.Close() + stoppedCh <- nil + close(stoppedCh) return } } diff --git a/vendor/modules.txt b/vendor/modules.txt index c89582328..ddf731e22 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -285,7 +285,7 @@ github.com/docker/distribution/registry/client/auth/challenge github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory -# github.com/docker/docker v20.10.7+incompatible +# github.com/docker/docker v20.10.8+incompatible github.com/docker/docker/api github.com/docker/docker/api/types github.com/docker/docker/api/types/blkiodev @@ -525,7 +525,7 @@ github.com/opencontainers/runtime-tools/generate github.com/opencontainers/runtime-tools/generate/seccomp github.com/opencontainers/runtime-tools/specerror github.com/opencontainers/runtime-tools/validate -# github.com/opencontainers/selinux v1.8.2 +# github.com/opencontainers/selinux v1.8.3 github.com/opencontainers/selinux/go-selinux github.com/opencontainers/selinux/go-selinux/label github.com/opencontainers/selinux/pkg/pwalk @@ -558,7 +558,7 @@ github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util # github.com/rivo/uniseg v0.2.0 github.com/rivo/uniseg -# github.com/rootless-containers/rootlesskit v0.14.2 +# github.com/rootless-containers/rootlesskit v0.14.4 github.com/rootless-containers/rootlesskit/pkg/api github.com/rootless-containers/rootlesskit/pkg/msgutil github.com/rootless-containers/rootlesskit/pkg/port |