diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2020-07-23 11:04:48 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-23 11:04:48 +0200 |
commit | dade9159e7a0f848e101221274faa3f4f03cf132 (patch) | |
tree | 4fc8aa7a9379260c103d86c37cfc0a2ad511a9e9 | |
parent | 2d24487ba244e5cd900f6aecc5d8896e1354d1ee (diff) | |
parent | 2faeb2189f81b6925d80aa8031cb5b19aa8618cb (diff) | |
download | podman-dade9159e7a0f848e101221274faa3f4f03cf132.tar.gz podman-dade9159e7a0f848e101221274faa3f4f03cf132.tar.bz2 podman-dade9159e7a0f848e101221274faa3f4f03cf132.zip |
Merge pull request #7054 from mheon/backports_203
Backports and Release Notes for v2.0.3
92 files changed, 1273 insertions, 538 deletions
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 898a09d70..96ee8a02c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,43 @@ # Release Notes +## 2.0.3 +### Features +- The `podman search` command now allows wildcards in search terms. +- The `podman play kube` command now supports the `IfNotPresent` pull type. + +### Changes +- The `--disable-content-trust` flag has been added to Podman for Docker compatibility. This is a Docker-specific option and has no effect in Podman; it is provided only to ensure command line compatibility for scripts ([#7034](https://github.com/containers/podman/issues/7034)). +- Setting a static IP address or MAC address for rootless containers and pods now causes an error; previously, they were silently ignored. +- The `/sys/dev` folder is now masked in containers to prevent a potential information leak from the host. + +### Bugfixes +- Fixed a bug where rootless Podman would select the wrong cgroup manager on cgroups v1 systems where the user in question had an active systemd user session ([#6982](https://github.com/containers/podman/issues/6982)). +- Fixed a bug where systems with Apparmor could not run privileged containers ([#6933](https://github.com/containers/podman/issues/6933)). +- Fixed a bug where ENTRYPOINT and CMD from images were improperly handled by `podman play kube` ([#6995](https://github.com/containers/podman/issues/6995)). +- Fixed a bug where the `--pids-limit` flag to `podman create` and `podman run` was parsed incorrectly and was unusable ([#6908](https://github.com/containers/podman/issues/6908)). +- Fixed a bug where the `podman system df` command would error if untagged images were present ([#7015](https://github.com/containers/podman/issues/7015)). +- Fixed a bug where the `podman images` command would display incorrect tags if a port number was included in the repository. +- Fixed a bug where Podman did not set a default umask and default rlimits ([#6989](https://github.com/containers/podman/issues/6989)). +- Fixed a bug where protocols in port mappings were not recognized unless they were lower-case ([#6948](https://github.com/containers/podman/issues/6948)). +- Fixed a bug where information on pod infra containers was not included in the output of `podman pod inspect`. +- Fixed a bug where Podman's systemd detection (activated by the enabled-by-default `--systemd=true` flag) would not flag a container for systemd mode if systemd was part of the entrypoint, not the command ([#6920](https://github.com/containers/podman/issues/6920)). +- Fixed a bug where `podman start --attach` was not defaulting `--sig-proxy` to true ([#6928](https://github.com/containers/podman/issues/6928)). +- Fixed a bug where `podman inspect` would show an incorrect command (`podman system service`, the command used to start the server) for containers created by a remote Podman client. +- Fixed a bug where the `podman exec` command with the remote client would not print output if the `-t` or `-i` flags where not provided. +- Fixed a bug where some variations of the `--format {{ json . }}` to `podman info` (involving added or removed whitespace) would not be accepted ([#6927](https://github.com/containers/podman/issues/6927)). +- Fixed a bug where Entrypoint could not be cleared at the command line (if unset via `--entrypoint=""`, it would be reset to the image's entrypoint) ([#6935](https://github.com/containers/podman/issues/6935)). + +### API +- Fixed a bug where the events endpoints (both libpod and compat) could potentially panic on parsing filters. +- Fixed a bug where the compat Create endpoint for containers did not properly handle Entrypoint and Command. +- Fixed a bug where the Logs endpoint for containers (both libpod and compat) would not properly handle client disconnect, resulting in high CPU usage. +- The type of filters on the compat events endpoint has been adjusted to match Docker's implementation ([#6899](https://github.com/containers/podman/issues/6899)). +- The idle connection counter now properly handles hijacked connections. +- All endpoints that hijack will now properly print headers per RFC 7230 standards. + +### Misc +- Updated containers/common to v0.14.6 + ## 2.0.2 ### Changes - The `podman system connection` command has been temporarily disabled, as it was not functioning as expected. diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index ee7f957cc..5f3198ed6 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -154,6 +154,10 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "device-write-iops", []string{}, "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", ) + createFlags.Bool( + "disable-content-trust", false, + "This is a Docker specific option and is a NOOP", + ) createFlags.String("entrypoint", "", "Overwrite the default ENTRYPOINT of the image", ) @@ -395,7 +399,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { ) createFlags.StringArrayVar( &cf.SecurityOpt, - "security-opt", containerConfig.SecurityOptions(), + "security-opt", []string{}, "Security Options", ) createFlags.String( diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index b4f786da2..aa8669e7a 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -365,9 +365,10 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.Annotations = annotations s.WorkDir = c.Workdir - entrypoint := []string{} userCommand := []string{} + var command []string if c.Entrypoint != nil { + entrypoint := []string{} if ep := *c.Entrypoint; len(ep) > 0 { // Check if entrypoint specified is json if err := json.Unmarshal([]byte(*c.Entrypoint), &entrypoint); err != nil { @@ -375,14 +376,14 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string } } s.Entrypoint = entrypoint - } - var command []string - - // Build the command - // If we have an entry point, it goes first - if c.Entrypoint != nil { + // Build the command + // If we have an entry point, it goes first command = entrypoint } + + // Include the command used to create the container. + s.ContainerCreateCommand = os.Args + if len(inputCommand) > 0 { // User command overrides data CMD command = append(command, inputCommand...) diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index 941588137..21f22b986 100644 --- a/cmd/podman/containers/start.go +++ b/cmd/podman/containers/start.go @@ -82,6 +82,7 @@ func start(cmd *cobra.Command, args []string) error { if cmd.Flag("sig-proxy").Changed { sigProxy = startOptions.SigProxy } + startOptions.SigProxy = sigProxy if sigProxy && !startOptions.Attach { return errors.Wrapf(define.ErrInvalidArg, "you cannot use sig-proxy without --attach") diff --git a/cmd/podman/early_init_linux.go b/cmd/podman/early_init_linux.go new file mode 100644 index 000000000..b43450a7f --- /dev/null +++ b/cmd/podman/early_init_linux.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "os" + "syscall" + + "github.com/containers/libpod/v2/libpod/define" + "github.com/pkg/errors" +) + +func setRLimits() error { + rlimits := new(syscall.Rlimit) + rlimits.Cur = define.RLimitDefaultValue + rlimits.Max = define.RLimitDefaultValue + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + return errors.Wrapf(err, "error getting rlimits") + } + rlimits.Cur = rlimits.Max + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + return errors.Wrapf(err, "error setting new rlimits") + } + } + return nil +} + +func setUMask() { + // Be sure we can create directories with 0755 mode. + syscall.Umask(0022) +} + +func earlyInitHook() { + if err := setRLimits(); err != nil { + fmt.Fprint(os.Stderr, "Failed to set rlimits: "+err.Error()) + } + + setUMask() +} diff --git a/cmd/podman/early_init_unsupported.go b/cmd/podman/early_init_unsupported.go new file mode 100644 index 000000000..4e748559f --- /dev/null +++ b/cmd/podman/early_init_unsupported.go @@ -0,0 +1,6 @@ +// +build !linux + +package main + +func earlyInitHook() { +} diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index 8d50986d5..94d03bd6f 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -1,7 +1,6 @@ package images import ( - "errors" "fmt" "os" "sort" @@ -11,9 +10,11 @@ import ( "time" "unicode" + "github.com/containers/image/v5/docker/reference" "github.com/containers/libpod/v2/cmd/podman/registry" "github.com/containers/libpod/v2/pkg/domain/entities" "github.com/docker/go-units" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -98,7 +99,10 @@ func images(cmd *cobra.Command, args []string) error { return err } - imgs := sortImages(summaries) + imgs, err := sortImages(summaries) + if err != nil { + return err + } switch { case listFlag.quiet: return writeID(imgs) @@ -170,14 +174,18 @@ func writeTemplate(imgs []imageReporter) error { return tmpl.Execute(w, imgs) } -func sortImages(imageS []*entities.ImageSummary) []imageReporter { +func sortImages(imageS []*entities.ImageSummary) ([]imageReporter, error) { imgs := make([]imageReporter, 0, len(imageS)) + var err error for _, e := range imageS { var h imageReporter if len(e.RepoTags) > 0 { for _, tag := range e.RepoTags { h.ImageSummary = *e - h.Repository, h.Tag = tokenRepoTag(tag) + h.Repository, h.Tag, err = tokenRepoTag(tag) + if err != nil { + return nil, errors.Wrapf(err, "error parsing repository tag %q:", tag) + } imgs = append(imgs, h) } } else { @@ -189,23 +197,32 @@ func sortImages(imageS []*entities.ImageSummary) []imageReporter { } sort.Slice(imgs, sortFunc(listFlag.sort, imgs)) - return imgs + return imgs, err } -func tokenRepoTag(tag string) (string, string) { - tokens := strings.Split(tag, ":") - switch len(tokens) { - case 0: - return tag, "" - case 1: - return tokens[0], "" - case 2: - return tokens[0], tokens[1] - case 3: - return tokens[0] + ":" + tokens[1], tokens[2] - default: - return "<N/A>", "" +func tokenRepoTag(ref string) (string, string, error) { + + if ref == "<none>:<none>" { + return "<none>", "<none>", nil + } + + repo, err := reference.Parse(ref) + if err != nil { + return "", "", err + } + + named, ok := repo.(reference.Named) + if !ok { + return ref, "", nil } + + tagged, ok := repo.(reference.Tagged) + if !ok { + return named.Name(), "", nil + } + + return named.Name(), tagged.Tag(), nil + } func sortFunc(key string, data []imageReporter) func(i, j int) bool { diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index 83bb186df..c10a351d8 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -82,6 +82,7 @@ func pullFlags(flags *pflag.FlagSet) { flags.StringVar(&pullOptions.CredentialsCLI, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") flags.StringVar(&pullOptions.OverrideArch, "override-arch", "", "Use `ARCH` instead of the architecture of the machine for choosing images") flags.StringVar(&pullOptions.OverrideOS, "override-os", "", "Use `OS` instead of the running OS for choosing images") + flags.Bool("disable-content-trust", false, "This is a Docker specific option and is a NOOP") flags.BoolVarP(&pullOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images") flags.StringVar(&pullOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") flags.BoolVar(&pullOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index fd91ab0ab..c25c33784 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -77,6 +77,7 @@ func pushFlags(flags *pflag.FlagSet) { flags.BoolVar(&pushOptions.Compress, "compress", false, "Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type as source)") flags.StringVar(&pushOptions.CredentialsCLI, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") flags.StringVar(&pushOptions.DigestFile, "digestfile", "", "Write the digest of the pushed image to the specified file") + flags.Bool("disable-content-trust", false, "This is a Docker specific option and is a NOOP") flags.StringVarP(&pushOptions.Format, "format", "f", "", "Manifest type (oci, v2s1, or v2s2) to use when pushing an image using the 'dir' transport (default is manifest type of source)") flags.BoolVarP(&pushOptions.Quiet, "quiet", "q", false, "Suppress output information when pushing images") flags.BoolVar(&pushOptions.RemoveSignatures, "remove-signatures", false, "Discard any pre-existing signatures in the image") diff --git a/cmd/podman/parse/json.go b/cmd/podman/parse/json.go new file mode 100644 index 000000000..95a6633b8 --- /dev/null +++ b/cmd/podman/parse/json.go @@ -0,0 +1,9 @@ +package parse + +import "regexp" + +var jsonFormatRegex = regexp.MustCompile(`^(\s*json\s*|\s*{{\s*json\s*\.\s*}}\s*)$`) + +func MatchesJSONFormat(s string) bool { + return jsonFormatRegex.Match([]byte(s)) +} diff --git a/cmd/podman/parse/json_test.go b/cmd/podman/parse/json_test.go new file mode 100644 index 000000000..5cad185fd --- /dev/null +++ b/cmd/podman/parse/json_test.go @@ -0,0 +1,30 @@ +package parse + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMatchesJSONFormat(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"json", true}, + {" json", true}, + {"json ", true}, + {" json ", true}, + {"{{json .}}", true}, + {"{{ json .}}", true}, + {"{{json . }}", true}, + {" {{ json . }} ", true}, + {"{{json }}", false}, + {"{{json .", false}, + {"json . }}", false}, + } + + for _, tt := range tests { + assert.Equal(t, tt.expected, MatchesJSONFormat(tt.input)) + } +} diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index 0e2a085fd..d57a2f2f7 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -149,6 +149,8 @@ func create(cmd *cobra.Command, args []string) error { } } + createOptions.CreateCommand = os.Args + if replace { if err := replacePod(createOptions.Name); err != nil { return err diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 7c54da91a..b2c9f9c2c 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -77,6 +77,7 @@ func init() { cobra.OnInitialize( loggingHook, syslogHook, + earlyInitHook, ) rootFlags(rootCmd, registry.PodmanConfig()) diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go index 699f7b55c..410b3455a 100644 --- a/cmd/podman/system/info.go +++ b/cmd/podman/system/info.go @@ -5,6 +5,7 @@ import ( "os" "text/template" + "github.com/containers/libpod/v2/cmd/podman/parse" "github.com/containers/libpod/v2/cmd/podman/registry" "github.com/containers/libpod/v2/cmd/podman/validate" "github.com/containers/libpod/v2/pkg/domain/entities" @@ -68,7 +69,7 @@ func info(cmd *cobra.Command, args []string) error { return err } - if inFormat == "json" { + if parse.MatchesJSONFormat(inFormat) { b, err := json.MarshalIndent(info, "", " ") if err != nil { return err diff --git a/cmd/podman/system/version.go b/cmd/podman/system/version.go index 5aac34699..9b70bc9f4 100644 --- a/cmd/podman/system/version.go +++ b/cmd/podman/system/version.go @@ -8,6 +8,7 @@ import ( "text/tabwriter" "github.com/containers/buildah/pkg/formats" + "github.com/containers/libpod/v2/cmd/podman/parse" "github.com/containers/libpod/v2/cmd/podman/registry" "github.com/containers/libpod/v2/cmd/podman/validate" "github.com/containers/libpod/v2/libpod/define" @@ -41,7 +42,7 @@ func version(cmd *cobra.Command, args []string) error { } switch { - case versionFormat == "json", versionFormat == "{{ json .}}": + case parse.MatchesJSONFormat(versionFormat): s, err := json.MarshalToString(versions) if err != nil { return err diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index ea2c7d8e0..fbdae83fa 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -41,7 +41,6 @@ case "${OS_RELEASE_ID}" in ubuntu) apt-get update apt-get install -y containers-common - sed -ie 's/^\(# \)\?apparmor_profile =.*/apparmor_profile = ""/' /etc/containers/containers.conf if [[ "$OS_RELEASE_VER" == "19" ]]; then apt-get purge -y --auto-remove golang* apt-get install -y golang-1.13 diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md index dc38caac0..24225bcb7 100644 --- a/docs/source/markdown/podman-build.1.md +++ b/docs/source/markdown/podman-build.1.md @@ -577,7 +577,7 @@ process. Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, Podman bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Podman - container. The `OPTIONS` are a comma delimited list and can be: + container. The `OPTIONS` are a comma delimited list and can be: <sup>[[1]](#Footnote1)</sup> * [rw|ro] * [z|Z|O] @@ -640,7 +640,7 @@ be specified only for bind mounted volumes and not for internal volumes or named volumes. For mount propagation to work on the source mount point (mount point where source dir is mounted on) has to have the right propagation properties. For shared volumes, the source mount point has to be shared. And for slave volumes, -the source mount has to be either shared or slave. +the source mount has to be either shared or slave. <sup>[[1]](#Footnote1)</sup> Use `df <source-dir>` to determine the source mount and then use `findmnt -o TARGET,PROPAGATION <source-mount-dir>` to determine propagation @@ -648,7 +648,7 @@ properties of source mount, if `findmnt` utility is not available, the source mo can be determined by looking at the mount entry in `/proc/self/mountinfo`. Look at `optional fields` and see if any propagation properties are specified. `shared:X` means the mount is `shared`, `master:X` means the mount is `slave` and if -nothing is there that means the mount is `private`. +nothing is there that means the mount is `private`. <sup>[[1]](#Footnote1)</sup> To change propagation properties of a mount point use the `mount` command. For example, to bind mount the source directory `/foo` do @@ -752,3 +752,6 @@ podman(1), buildah(1), containers-registries.conf(5), crun(8), runc(8), useradd( May 2018, Minor revisions added by Joe Doss <joe@solidadmin.com> December 2017, Originally compiled by Tom Sweeney <tsweeney@redhat.com> + +## FOOTNOTES +<a name="Footnote1">1</a>: The Podman project is committed to inclusivity, a core value of open source. The `master` and `slave` mount propagation terminology used here is problematic and divisive, and should be changed. However, these terms are currently used within the Linux kernel and must be used as-is at this time. When the kernel maintainers rectify this usage, Podman will follow suit immediately. diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 6d8686d24..2b383f51b 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -231,6 +231,12 @@ Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sd Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000) +**--disable-content-trust** + +This is a Docker specific option to disable image verification to a Docker +registry and is not supported by Podman. This flag is a NOOP and provided +solely for scripting compatibility. + **--dns**=*dns* Set custom DNS servers. Invalid if using **--dns** and **--network** that is set to 'none' or 'container:<name|id>'. @@ -485,7 +491,7 @@ Tune a container's memory swappiness behavior. Accepts an integer between 0 and Attach a filesystem mount to the container -Current supported mount TYPES are `bind`, `volume`, and `tmpfs`. +Current supported mount TYPES are `bind`, `volume`, and `tmpfs`. <sup>[[1]](#Footnote1)</sup> e.g. @@ -866,7 +872,7 @@ Set the UTS mode for the container Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, podman bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the podman -container. The `OPTIONS` are a comma delimited list and can be: +container. The `OPTIONS` are a comma delimited list and can be: <sup>[[1]](#Footnote1)</sup> * [rw|ro] * [z|Z] @@ -918,7 +924,7 @@ this behavior by specifying a volume mount propagation property. Making a volume `shared` mounts done under that volume inside container will be visible on host and vice versa. Making a volume `slave` enables only one way mount propagation and that is mounts done on host under that volume -will be visible inside container but not the other way around. +will be visible inside container but not the other way around. <sup>[[1]](#Footnote1)</sup> To control mount propagation property of volume one can use `:[r]shared`, `:[r]slave` or `:[r]private` propagation flag. Propagation property can @@ -926,7 +932,7 @@ be specified only for bind mounted volumes and not for internal volumes or named volumes. For mount propagation to work source mount point (mount point where source dir is mounted on) has to have right propagation properties. For shared volumes, source mount point has to be shared. And for slave volumes, -source mount has to be either shared or slave. +source mount has to be either shared or slave. <sup>[[1]](#Footnote1)</sup> If you want to recursively mount a volume and all of its submounts into a container, then you can use the `rbind` option. By default the bind option is @@ -953,7 +959,7 @@ properties of source mount. If `findmnt` utility is not available, then one can look at mount entry for source mount point in `/proc/self/mountinfo`. Look at `optional fields` and see if any propagation properties are specified. `shared:X` means mount is `shared`, `master:X` means mount is `slave` and if -nothing is there that means mount is `private`. +nothing is there that means mount is `private`. <sup>[[1]](#Footnote1)</sup> To change propagation properties of a mount point use `mount` command. For example, if one wants to bind mount source directory `/foo` one can do @@ -1091,3 +1097,6 @@ November 2014, updated by Sven Dowideit <SvenDowideit@home.org.au> September 2014, updated by Sven Dowideit <SvenDowideit@home.org.au> August 2014, updated by Sven Dowideit <SvenDowideit@home.org.au> + +## FOOTNOTES +<a name="Footnote1">1</a>: The Podman project is committed to inclusivity, a core value of open source. The `master` and `slave` mount propagation terminology used here is problematic and divisive, and should be changed. However, these terms are currently used within the Linux kernel and must be used as-is at this time. When the kernel maintainers rectify this usage, Podman will follow suit immediately. diff --git a/docs/source/markdown/podman-image-trust.1.md b/docs/source/markdown/podman-image-trust.1.md index 435d117f1..8b80ca602 100644 --- a/docs/source/markdown/podman-image-trust.1.md +++ b/docs/source/markdown/podman-image-trust.1.md @@ -30,8 +30,8 @@ If no configuration is found for any of these scopes, the default value (specifi Trust **type** provides a way to: -Whitelist ("accept") or -Blacklist ("reject") registries or +Allowlist ("accept") or +Denylist ("reject") registries or Require signature (“signedBy”). Trust may be updated using the command **podman image trust set** for an existing trust scope. diff --git a/docs/source/markdown/podman-pod-create.1.md b/docs/source/markdown/podman-pod-create.1.md index 1401400bb..d60fc65fe 100644 --- a/docs/source/markdown/podman-pod-create.1.md +++ b/docs/source/markdown/podman-pod-create.1.md @@ -108,7 +108,7 @@ If another pod with the same name already exists, replace and remove it. The de **--share**=*namespace* -A comma delimited list of kernel namespaces to share. If none or "" is specified, no namespaces will be shared. The namespaces to choose from are ipc, net, pid, user, uts. +A comma delimited list of kernel namespaces to share. If none or "" is specified, no namespaces will be shared. The namespaces to choose from are ipc, net, pid, uts. The operator can identify a pod in three ways: UUID long identifier (“f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778”) diff --git a/docs/source/markdown/podman-pull.1.md b/docs/source/markdown/podman-pull.1.md index 5d941219a..201b10aa6 100644 --- a/docs/source/markdown/podman-pull.1.md +++ b/docs/source/markdown/podman-pull.1.md @@ -73,6 +73,12 @@ The [username[:password]] to use to authenticate with the registry if required. If one or both values are not supplied, a command line prompt will appear and the value can be entered. The password is entered without echo. +**--disable-content-trust** + +This is a Docker specific option to disable image verification to a Docker +registry and is not supported by Podman. This flag is a NOOP and provided +solely for scripting compatibility. + **--override-os**=*OS* Use OS instead of the running OS for choosing images diff --git a/docs/source/markdown/podman-push.1.md b/docs/source/markdown/podman-push.1.md index f029c8db1..fffd76801 100644 --- a/docs/source/markdown/podman-push.1.md +++ b/docs/source/markdown/podman-push.1.md @@ -71,6 +71,12 @@ Note: This flag can only be set when using the **dir** transport After copying the image, write the digest of the resulting image to the file. (Not available for remote commands) +**--disable-content-trust** + +This is a Docker specific option to disable image verification to a Docker +registry and is not supported by Podman. This flag is a NOOP and provided +solely for scripting compatibility. + **--format**, **-f**=*format* Manifest Type (oci, v2s1, or v2s2) to use when pushing an image to a directory using the 'dir:' transport (default is manifest type of source) diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 6632ac2d1..7b93eb025 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -245,6 +245,12 @@ Limit write rate (in bytes per second) to a device (e.g. **--device-write-bps=/d Limit write rate (in IO operations per second) to a device (e.g. **--device-write-iops=/dev/sda:1000**). +**--disable-content-trust** + +This is a Docker specific option to disable image verification to a Docker +registry and is not supported by Podman. This flag is a NOOP and provided +solely for scripting compatibility. + **--dns**=*ipaddr* Set custom DNS servers. Invalid if using **--dns** with **--network** that is set to **none** or **container:**_id_. @@ -493,7 +499,7 @@ Tune a container's memory swappiness behavior. Accepts an integer between *0* an Attach a filesystem mount to the container -Current supported mount TYPEs are **bind**, **volume**, and **tmpfs**. +Current supported mount TYPEs are **bind**, **volume**, and **tmpfs**. <sup>[[1]](#Footnote1)</sup> e.g. @@ -737,7 +743,7 @@ Security Options - **label=disable**: Turn off label separation for the container - **no-new-privileges**: Disable container processes from gaining additional privileges - **seccomp=unconfined**: Turn off seccomp confinement for the container -- **seccomp**=_profile.json_: Whitelisted syscalls seccomp JSON file to be used as a seccomp filter +- **seccomp**=_profile.json_: Allowed syscall list seccomp JSON file to be used as a seccomp filter Note: Labeling can be disabled for all containers by setting **label=false** in the **containers.conf**(5) file. @@ -903,7 +909,7 @@ container. Similarly, _source-volume_:_/container-dir_ will mount the volume in the host to the container. If no such named volume exists, Podman will create one. -The _options_ is a comma delimited list and can be: +The _options_ is a comma delimited list and can be: <sup>[[1]](#Footnote1)</sup> * **rw**|**ro** * **z**|**Z** @@ -953,7 +959,7 @@ this behavior by specifying a volume mount propagation property. Making a volume shared mounts done under that volume inside container will be visible on host and vice versa. Making a volume **slave** enables only one way mount propagation and that is mounts done on host under that volume -will be visible inside container but not the other way around. +will be visible inside container but not the other way around. <sup>[[1]](#Footnote1)</sup> To control mount propagation property of volume one can use [**r**]**shared**, [**r**]**slave** or [**r**]**private** propagation flag. Propagation property can @@ -961,7 +967,7 @@ be specified only for bind mounted volumes and not for internal volumes or named volumes. For mount propagation to work source mount point (mount point where source dir is mounted on) has to have right propagation properties. For shared volumes, source mount point has to be shared. And for slave volumes, -source mount has to be either shared or slave. +source mount has to be either shared or slave. <sup>[[1]](#Footnote1)</sup> If you want to recursively mount a volume and all of its submounts into a container, then you can use the **rbind** option. By default the bind option is @@ -988,7 +994,7 @@ properties of source mount. If **findmnt**(1) utility is not available, then one can look at mount entry for source mount point in _/proc/self/mountinfo_. Look at the "optional fields" and see if any propagation properties are specified. In there, **shared:N** means the mount is shared, **master:N** means mount -is slave, and if nothing is there, the mount is private. +is slave, and if nothing is there, the mount is private. <sup>[[1]](#Footnote1)</sup> To change propagation properties of a mount point, use **mount**(8) command. For example, if one wants to bind mount source directory _/foo_, one can do @@ -1371,3 +1377,6 @@ July 2014, updated by Sven Dowideit <SvenDowideit@home.org.au> June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au> April 2014, Originally compiled by William Henry <whenry@redhat.com> based on docker.com source material and internal work. + +## FOOTNOTES +<a name="Footnote1">1</a>: The Podman project is committed to inclusivity, a core value of open source. The `master` and `slave` mount propagation terminology used here is problematic and divisive, and should be changed. However, these terms are currently used within the Linux kernel and must be used as-is at this time. When the kernel maintainers rectify this usage, Podman will follow suit immediately. diff --git a/libpod/container_api.go b/libpod/container_api.go index b37b05ff2..487f75e67 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -29,7 +29,7 @@ import ( // Init requires that all dependency containers be started (e.g. pod infra // containers). The `recursive` parameter will, if set to true, start these // dependency containers before initializing this container. -func (c *Container) Init(ctx context.Context, recursive bool) (err error) { +func (c *Container) Init(ctx context.Context, recursive bool) error { span, _ := opentracing.StartSpanFromContext(ctx, "containerInit") span.SetTag("struct", "container") defer span.Finish() @@ -85,7 +85,7 @@ func (c *Container) Init(ctx context.Context, recursive bool) (err error) { // Start requites that all dependency containers (e.g. pod infra containers) be // running before being run. The recursive parameter, if set, will start all // dependencies before starting this container. -func (c *Container) Start(ctx context.Context, recursive bool) (err error) { +func (c *Container) Start(ctx context.Context, recursive bool) error { span, _ := opentracing.StartSpanFromContext(ctx, "containerStart") span.SetTag("struct", "container") defer span.Finish() @@ -112,7 +112,7 @@ func (c *Container) Start(ctx context.Context, recursive bool) (err error) { // Attach call occurs before Start). // In overall functionality, it is identical to the Start call, with the added // side effect that an attach session will also be started. -func (c *Container) StartAndAttach(ctx context.Context, streams *define.AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, recursive bool) (attachResChan <-chan error, err error) { +func (c *Container) StartAndAttach(ctx context.Context, streams *define.AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, recursive bool) (<-chan error, error) { if !c.batched { c.lock.Lock() defer c.lock.Unlock() @@ -150,7 +150,7 @@ func (c *Container) StartAndAttach(ctx context.Context, streams *define.AttachSt } // RestartWithTimeout restarts a running container and takes a given timeout in uint -func (c *Container) RestartWithTimeout(ctx context.Context, timeout uint) (err error) { +func (c *Container) RestartWithTimeout(ctx context.Context, timeout uint) error { if !c.batched { c.lock.Lock() defer c.lock.Unlock() @@ -160,7 +160,7 @@ func (c *Container) RestartWithTimeout(ctx context.Context, timeout uint) (err e } } - if err = c.checkDependenciesAndHandleError(); err != nil { + if err := c.checkDependenciesAndHandleError(); err != nil { return err } @@ -353,7 +353,7 @@ func (c *Container) HTTPAttach(httpCon net.Conn, httpBuf *bufio.ReadWriter, stre logOpts.WaitGroup.Wait() close(logChan) }() - if err := c.ReadLog(logOpts, logChan); err != nil { + if err := c.ReadLog(context.Background(), logOpts, logChan); err != nil { return err } logrus.Debugf("Done reading logs for container %s, %d bytes", c.ID(), logSize) @@ -760,7 +760,7 @@ func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointO } // Restore restores a container -func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOptions) (err error) { +func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOptions) error { logrus.Debugf("Trying to restore container %s", c.ID()) if !c.batched { c.lock.Lock() diff --git a/libpod/container_exec.go b/libpod/container_exec.go index 69da6fcfe..bd04ee9b9 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -779,25 +779,25 @@ func (c *Container) execOCILog(sessionID string) string { } // create a bundle path and associated files for an exec session -func (c *Container) createExecBundle(sessionID string) (err error) { +func (c *Container) createExecBundle(sessionID string) (retErr error) { bundlePath := c.execBundlePath(sessionID) - if createErr := os.MkdirAll(bundlePath, execDirPermission); createErr != nil { - return createErr + if err := os.MkdirAll(bundlePath, execDirPermission); err != nil { + return err } defer func() { - if err != nil { - if err2 := os.RemoveAll(bundlePath); err != nil { - logrus.Warnf("error removing exec bundle after creation caused another error: %v", err2) + if retErr != nil { + if err := os.RemoveAll(bundlePath); err != nil { + logrus.Warnf("error removing exec bundle after creation caused another error: %v", err) } } }() - if err2 := os.MkdirAll(c.execExitFileDir(sessionID), execDirPermission); err2 != nil { + if err := os.MkdirAll(c.execExitFileDir(sessionID), execDirPermission); err != nil { // The directory is allowed to exist - if !os.IsExist(err2) { - err = errors.Wrapf(err2, "error creating OCI runtime exit file path %s", c.execExitFileDir(sessionID)) + if !os.IsExist(err) { + return errors.Wrapf(err, "error creating OCI runtime exit file path %s", c.execExitFileDir(sessionID)) } } - return + return nil } // readExecExitCode reads the exit file for an exec session and returns diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 03684ddec..cd2e41be2 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -289,6 +289,7 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp ctrConfig.OpenStdin = c.config.Stdin ctrConfig.Image = c.config.RootfsImageName + ctrConfig.SystemdMode = c.config.Systemd // Leave empty is not explicitly overwritten by user if len(c.config.Command) != 0 { @@ -610,22 +611,11 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named // Port bindings. // Only populate if we're using CNI to configure the network. - portBindings := make(map[string][]define.InspectHostPort) if c.config.CreateNetNS { - for _, port := range c.config.PortMappings { - key := fmt.Sprintf("%d/%s", port.ContainerPort, port.Protocol) - hostPorts := portBindings[key] - if hostPorts == nil { - hostPorts = []define.InspectHostPort{} - } - hostPorts = append(hostPorts, define.InspectHostPort{ - HostIP: port.HostIP, - HostPort: fmt.Sprintf("%d", port.HostPort), - }) - portBindings[key] = hostPorts - } + hostConfig.PortBindings = makeInspectPortBindings(c.config.PortMappings) + } else { + hostConfig.PortBindings = make(map[string][]define.InspectHostPort) } - hostConfig.PortBindings = portBindings // Cap add and cap drop. // We need a default set of capabilities to compare against. diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 7a547e565..c44ba5fe6 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -209,7 +209,7 @@ func (c *Container) handleExitFile(exitFile string, fi os.FileInfo) error { // Handle container restart policy. // This is called when a container has exited, and was not explicitly stopped by // an API call to stop the container or pod it is in. -func (c *Container) handleRestartPolicy(ctx context.Context) (restarted bool, err error) { +func (c *Container) handleRestartPolicy(ctx context.Context) (_ bool, retErr error) { // If we did not get a restart policy match, exit immediately. // Do the same if we're not a policy that restarts. if !c.state.RestartPolicyMatch || @@ -240,7 +240,7 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (restarted bool, er logrus.Debugf("Restarting container %s due to restart policy %s", c.ID(), c.config.RestartPolicy) // Need to check if dependencies are alive. - if err = c.checkDependenciesAndHandleError(); err != nil { + if err := c.checkDependenciesAndHandleError(); err != nil { return false, err } @@ -262,9 +262,9 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (restarted bool, er } defer func() { - if err != nil { - if err2 := c.cleanup(ctx); err2 != nil { - logrus.Errorf("error cleaning up container %s: %v", c.ID(), err2) + if retErr != nil { + if err := c.cleanup(ctx); err != nil { + logrus.Errorf("error cleaning up container %s: %v", c.ID(), err) } } }() @@ -767,7 +767,7 @@ func (c *Container) save() error { // Checks the container is in the right state, then initializes the container in preparation to start the container. // If recursive is true, each of the containers dependencies will be started. // Otherwise, this function will return with error if there are dependencies of this container that aren't running. -func (c *Container) prepareToStart(ctx context.Context, recursive bool) (err error) { +func (c *Container) prepareToStart(ctx context.Context, recursive bool) (retErr error) { // Container must be created or stopped to be started if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated, define.ContainerStateStopped, define.ContainerStateExited) { return errors.Wrapf(define.ErrCtrStateInvalid, "container %s must be in Created or Stopped state to be started", c.ID()) @@ -784,9 +784,9 @@ func (c *Container) prepareToStart(ctx context.Context, recursive bool) (err err } defer func() { - if err != nil { - if err2 := c.cleanup(ctx); err2 != nil { - logrus.Errorf("error cleaning up container %s: %v", c.ID(), err2) + if retErr != nil { + if err := c.cleanup(ctx); err != nil { + logrus.Errorf("error cleaning up container %s: %v", c.ID(), err) } } }() @@ -1132,7 +1132,7 @@ func (c *Container) reinit(ctx context.Context, retainRetries bool) error { // Initialize (if necessary) and start a container // Performs all necessary steps to start a container that is not running // Does not lock or check validity -func (c *Container) initAndStart(ctx context.Context) (err error) { +func (c *Container) initAndStart(ctx context.Context) (retErr error) { // If we are ContainerStateUnknown, throw an error if c.state.State == define.ContainerStateUnknown { return errors.Wrapf(define.ErrCtrStateInvalid, "container %s is in an unknown state", c.ID()) @@ -1150,9 +1150,9 @@ func (c *Container) initAndStart(ctx context.Context) (err error) { } defer func() { - if err != nil { - if err2 := c.cleanup(ctx); err2 != nil { - logrus.Errorf("error cleaning up container %s: %v", c.ID(), err2) + if retErr != nil { + if err := c.cleanup(ctx); err != nil { + logrus.Errorf("error cleaning up container %s: %v", c.ID(), err) } } }() @@ -1321,7 +1321,7 @@ func (c *Container) unpause() error { } // Internal, non-locking function to restart a container -func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err error) { +func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (retErr error) { if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStateStopped, define.ContainerStateExited) { return errors.Wrapf(define.ErrCtrStateInvalid, "unable to restart a container in a paused or unknown state") } @@ -1358,9 +1358,9 @@ func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err e } } defer func() { - if err != nil { - if err2 := c.cleanup(ctx); err2 != nil { - logrus.Errorf("error cleaning up container %s: %v", c.ID(), err2) + if retErr != nil { + if err := c.cleanup(ctx); err != nil { + logrus.Errorf("error cleaning up container %s: %v", c.ID(), err) } } }() @@ -1682,7 +1682,7 @@ func (c *Container) cleanup(ctx context.Context) error { // delete deletes the container and runs any configured poststop // hooks. -func (c *Container) delete(ctx context.Context) (err error) { +func (c *Container) delete(ctx context.Context) error { span, _ := opentracing.StartSpanFromContext(ctx, "delete") span.SetTag("struct", "container") defer span.Finish() @@ -1701,7 +1701,7 @@ func (c *Container) delete(ctx context.Context) (err error) { // postDeleteHooks runs the poststop hooks (if any) as specified by // the OCI Runtime Specification (which requires them to run // post-delete, despite the stage name). -func (c *Container) postDeleteHooks(ctx context.Context) (err error) { +func (c *Container) postDeleteHooks(ctx context.Context) error { span, _ := opentracing.StartSpanFromContext(ctx, "postDeleteHooks") span.SetTag("struct", "container") defer span.Finish() @@ -1824,7 +1824,7 @@ func (c *Container) saveSpec(spec *spec.Spec) error { } // Warning: precreate hooks may alter 'config' in place. -func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (extensionStageHooks map[string][]spec.Hook, err error) { +func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (map[string][]spec.Hook, error) { allHooks := make(map[string][]spec.Hook) if c.runtime.config.Engine.HooksDir == nil { if rootless.IsRootless() { @@ -1938,7 +1938,7 @@ func (c *Container) checkReadyForRemoval() error { // writeJSONFile marshalls and writes the given data to a JSON file // in the bundle path -func (c *Container) writeJSONFile(v interface{}, file string) (err error) { +func (c *Container) writeJSONFile(v interface{}, file string) error { fileJSON, err := json.MarshalIndent(v, "", " ") if err != nil { return errors.Wrapf(err, "error writing JSON to %s for container %s", file, c.ID()) @@ -1953,7 +1953,7 @@ func (c *Container) writeJSONFile(v interface{}, file string) (err error) { // prepareCheckpointExport writes the config and spec to // JSON files for later export -func (c *Container) prepareCheckpointExport() (err error) { +func (c *Container) prepareCheckpointExport() error { // save live config if err := c.writeJSONFile(c.Config(), "config.dump"); err != nil { return err diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index cb70aea62..cfcf9b823 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -244,7 +244,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } // Apply AppArmor checks and load the default profile if needed. - if !c.config.Privileged { + if len(c.config.Spec.Process.ApparmorProfile) > 0 { updatedProfile, err := apparmor.CheckProfileAndLoadDefault(c.config.Spec.Process.ApparmorProfile) if err != nil { return nil, err @@ -610,7 +610,7 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr return nil } -func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) (err error) { +func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) error { if (len(c.config.NamedVolumes) > 0) || (len(c.Dependencies()) > 0) { return errors.Errorf("Cannot export checkpoints of containers with named volumes or dependencies") } @@ -721,7 +721,7 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) (err error) return nil } -func (c *Container) checkpointRestoreSupported() (err error) { +func (c *Container) checkpointRestoreSupported() error { if !criu.CheckForCriu() { return errors.Errorf("Checkpoint/Restore requires at least CRIU %d", criu.MinCriuVersion) } @@ -731,7 +731,7 @@ func (c *Container) checkpointRestoreSupported() (err error) { return nil } -func (c *Container) checkpointRestoreLabelLog(fileName string) (err error) { +func (c *Container) checkpointRestoreLabelLog(fileName string) error { // Create the CRIU log file and label it dumpLog := filepath.Join(c.bundlePath(), fileName) @@ -748,7 +748,7 @@ func (c *Container) checkpointRestoreLabelLog(fileName string) (err error) { return nil } -func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) (err error) { +func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) error { if err := c.checkpointRestoreSupported(); err != nil { return err } @@ -818,7 +818,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO return c.save() } -func (c *Container) importCheckpoint(input string) (err error) { +func (c *Container) importCheckpoint(input string) error { archiveFile, err := os.Open(input) if err != nil { return errors.Wrapf(err, "Failed to open checkpoint archive %s for import", input) @@ -847,8 +847,7 @@ func (c *Container) importCheckpoint(input string) (err error) { return nil } -func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) (err error) { - +func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) (retErr error) { if err := c.checkpointRestoreSupported(); err != nil { return err } @@ -858,7 +857,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti } if options.TargetFile != "" { - if err = c.importCheckpoint(options.TargetFile); err != nil { + if err := c.importCheckpoint(options.TargetFile); err != nil { return err } } @@ -944,9 +943,9 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti } defer func() { - if err != nil { - if err2 := c.cleanup(ctx); err2 != nil { - logrus.Errorf("error cleaning up container %s: %v", c.ID(), err2) + if retErr != nil { + if err := c.cleanup(ctx); err != nil { + logrus.Errorf("error cleaning up container %s: %v", c.ID(), err) } } }() diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go index a42c1d735..e6d94104c 100644 --- a/libpod/container_internal_unsupported.go +++ b/libpod/container_internal_unsupported.go @@ -18,7 +18,7 @@ func (c *Container) unmountSHM(mount string) error { return define.ErrNotImplemented } -func (c *Container) prepare() (err error) { +func (c *Container) prepare() error { return define.ErrNotImplemented } diff --git a/libpod/container_log.go b/libpod/container_log.go index 97936c683..80f8e6e50 100644 --- a/libpod/container_log.go +++ b/libpod/container_log.go @@ -1,6 +1,7 @@ package libpod import ( + "context" "fmt" "os" "time" @@ -13,9 +14,9 @@ import ( ) // Log is a runtime function that can read one or more container logs. -func (r *Runtime) Log(containers []*Container, options *logs.LogOptions, logChannel chan *logs.LogLine) error { +func (r *Runtime) Log(ctx context.Context, containers []*Container, options *logs.LogOptions, logChannel chan *logs.LogLine) error { for _, ctr := range containers { - if err := ctr.ReadLog(options, logChannel); err != nil { + if err := ctr.ReadLog(ctx, options, logChannel); err != nil { return err } } @@ -23,25 +24,25 @@ func (r *Runtime) Log(containers []*Container, options *logs.LogOptions, logChan } // ReadLog reads a containers log based on the input options and returns loglines over a channel. -func (c *Container) ReadLog(options *logs.LogOptions, logChannel chan *logs.LogLine) error { +func (c *Container) ReadLog(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error { switch c.LogDriver() { case define.NoLogging: return errors.Wrapf(define.ErrNoLogs, "this container is using the 'none' log driver, cannot read logs") case define.JournaldLogging: // TODO Skip sending logs until journald logs can be read - return c.readFromJournal(options, logChannel) + return c.readFromJournal(ctx, options, logChannel) case define.JSONLogging: // TODO provide a separate implementation of this when Conmon // has support. fallthrough case define.KubernetesLogging, "": - return c.readFromLogFile(options, logChannel) + return c.readFromLogFile(ctx, options, logChannel) default: return errors.Wrapf(define.ErrInternal, "unrecognized log driver %q, cannot read logs", c.LogDriver()) } } -func (c *Container) readFromLogFile(options *logs.LogOptions, logChannel chan *logs.LogLine) error { +func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error { t, tailLog, err := logs.GetLogFile(c.LogPath(), options) if err != nil { // If the log file does not exist, this is not fatal. @@ -62,8 +63,17 @@ func (c *Container) readFromLogFile(options *logs.LogOptions, logChannel chan *l } go func() { + defer options.WaitGroup.Done() + var partial string for line := range t.Lines { + select { + case <-ctx.Done(): + // the consumer has cancelled + return + default: + // fallthrough + } nll, err := logs.NewLogLine(line.Text) if err != nil { logrus.Error(err) @@ -82,7 +92,6 @@ func (c *Container) readFromLogFile(options *logs.LogOptions, logChannel chan *l logChannel <- nll } } - options.WaitGroup.Done() }() // Check if container is still running or paused if options.Follow { diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go index fad3bf87c..00b2039a9 100644 --- a/libpod/container_log_linux.go +++ b/libpod/container_log_linux.go @@ -4,6 +4,7 @@ package libpod import ( + "context" "fmt" "io" "math" @@ -29,7 +30,7 @@ const ( bufLen = 16384 ) -func (c *Container) readFromJournal(options *logs.LogOptions, logChannel chan *logs.LogLine) error { +func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error { var config journal.JournalReaderConfig if options.Tail < 0 { config.NumFromTail = math.MaxUint64 @@ -65,13 +66,24 @@ func (c *Container) readFromJournal(options *logs.LogOptions, logChannel chan *l if options.Follow { go func() { + done := make(chan bool) + until := make(chan time.Time) + go func() { + select { + case <-ctx.Done(): + until <- time.Time{} + case <-done: + // nothing to do anymore + } + }() follower := FollowBuffer{logChannel} - err := r.Follow(nil, follower) + err := r.Follow(until, follower) if err != nil { logrus.Debugf(err.Error()) } r.Close() options.WaitGroup.Done() + done <- true return }() return nil diff --git a/libpod/container_log_unsupported.go b/libpod/container_log_unsupported.go index 18882720a..f3b36619e 100644 --- a/libpod/container_log_unsupported.go +++ b/libpod/container_log_unsupported.go @@ -3,11 +3,13 @@ package libpod import ( + "context" + "github.com/containers/libpod/v2/libpod/define" "github.com/containers/libpod/v2/libpod/logs" "github.com/pkg/errors" ) -func (c *Container) readFromJournal(options *logs.LogOptions, logChannel chan *logs.LogLine) error { +func (c *Container) readFromJournal(_ context.Context, _ *logs.LogOptions, _ chan *logs.LogLine) error { return errors.Wrapf(define.ErrOSNotSupported, "Journald logging only enabled with systemd on linux") } diff --git a/libpod/define/config.go b/libpod/define/config.go index 900a363d8..64b24d9e2 100644 --- a/libpod/define/config.go +++ b/libpod/define/config.go @@ -75,3 +75,13 @@ const JSONLogging = "json-file" // NoLogging is the string conmon expects when specifying to use no log driver whatsoever const NoLogging = "none" + +// Strings used for --sdnotify option to podman +const ( + SdNotifyModeContainer = "container" + SdNotifyModeConmon = "conmon" + SdNotifyModeIgnore = "ignore" +) + +// DefaultRlimitValue is the value set by default for nofile and nproc +const RLimitDefaultValue = uint64(1048576) diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go index a3cf4304f..7807f9e8b 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -54,6 +54,10 @@ type InspectContainerConfig struct { // CreateCommand is the full command plus arguments of the process the // container has been created with. CreateCommand []string `json:"CreateCommand,omitempty"` + // SystemdMode is whether the container is running in systemd mode. In + // systemd mode, the container configuration is customized to optimize + // running systemd in the container. + SystemdMode bool `json:"SystemdMode,omitempty"` } // InspectRestartPolicy holds information about the container's restart policy. @@ -628,3 +632,56 @@ type InspectContainerData struct { Config *InspectContainerConfig `json:"Config"` HostConfig *InspectContainerHostConfig `json:"HostConfig"` } + +// InspectExecSession contains information about a given exec session. +type InspectExecSession struct { + // CanRemove is legacy and used purely for compatibility reasons. + // Will always be set to true, unless the exec session is running. + CanRemove bool `json:"CanRemove"` + // ContainerID is the ID of the container this exec session is attached + // to. + ContainerID string `json:"ContainerID"` + // DetachKeys are the detach keys used by the exec session. + // If set to "" the default keys are being used. + // Will show "<none>" if no detach keys are set. + DetachKeys string `json:"DetachKeys"` + // ExitCode is the exit code of the exec session. Will be set to 0 if + // the exec session has not yet exited. + ExitCode int `json:"ExitCode"` + // ID is the ID of the exec session. + ID string `json:"ID"` + // OpenStderr is whether the container's STDERR stream will be attached. + // Always set to true if the exec session created a TTY. + OpenStderr bool `json:"OpenStderr"` + // OpenStdin is whether the container's STDIN stream will be attached + // to. + OpenStdin bool `json:"OpenStdin"` + // OpenStdout is whether the container's STDOUT stream will be attached. + // Always set to true if the exec session created a TTY. + OpenStdout bool `json:"OpenStdout"` + // Running is whether the exec session is running. + Running bool `json:"Running"` + // Pid is the PID of the exec session's process. + // Will be set to 0 if the exec session is not running. + Pid int `json:"Pid"` + // ProcessConfig contains information about the exec session's process. + ProcessConfig *InspectExecProcess `json:"ProcessConfig"` +} + +// InspectExecProcess contains information about the process in a given exec +// session. +type InspectExecProcess struct { + // Arguments are the arguments to the entrypoint command of the exec + // session. + Arguments []string `json:"arguments"` + // Entrypoint is the entrypoint for the exec session (the command that + // will be executed in the container). + Entrypoint string `json:"entrypoint"` + // Privileged is whether the exec session will be started with elevated + // privileges. + Privileged bool `json:"privileged"` + // Tty is whether the exec session created a terminal. + Tty bool `json:"tty"` + // User is the user the exec session was started as. + User string `json:"user"` +} diff --git a/libpod/define/ctr_inspect.go b/libpod/define/ctr_inspect.go deleted file mode 100644 index b7cd13f82..000000000 --- a/libpod/define/ctr_inspect.go +++ /dev/null @@ -1,54 +0,0 @@ -package define - -// InspectExecSession contains information about a given exec session. -type InspectExecSession struct { - // CanRemove is legacy and used purely for compatibility reasons. - // Will always be set to true, unless the exec session is running. - CanRemove bool `json:"CanRemove"` - // ContainerID is the ID of the container this exec session is attached - // to. - ContainerID string `json:"ContainerID"` - // DetachKeys are the detach keys used by the exec session. - // If set to "" the default keys are being used. - // Will show "<none>" if no detach keys are set. - DetachKeys string `json:"DetachKeys"` - // ExitCode is the exit code of the exec session. Will be set to 0 if - // the exec session has not yet exited. - ExitCode int `json:"ExitCode"` - // ID is the ID of the exec session. - ID string `json:"ID"` - // OpenStderr is whether the container's STDERR stream will be attached. - // Always set to true if the exec session created a TTY. - OpenStderr bool `json:"OpenStderr"` - // OpenStdin is whether the container's STDIN stream will be attached - // to. - OpenStdin bool `json:"OpenStdin"` - // OpenStdout is whether the container's STDOUT stream will be attached. - // Always set to true if the exec session created a TTY. - OpenStdout bool `json:"OpenStdout"` - // Running is whether the exec session is running. - Running bool `json:"Running"` - // Pid is the PID of the exec session's process. - // Will be set to 0 if the exec session is not running. - Pid int `json:"Pid"` - // ProcessConfig contains information about the exec session's process. - ProcessConfig *InspectExecProcess `json:"ProcessConfig"` -} - -// InspectExecProcess contains information about the process in a given exec -// session. -type InspectExecProcess struct { - // Arguments are the arguments to the entrypoint command of the exec - // session. - Arguments []string `json:"arguments"` - // Entrypoint is the entrypoint for the exec session (the command that - // will be executed in the container). - Entrypoint string `json:"entrypoint"` - // Privileged is whether the exec session will be started with elevated - // privileges. - Privileged bool `json:"privileged"` - // Tty is whether the exec session created a terminal. - Tty bool `json:"tty"` - // User is the user the exec session was started as. - User string `json:"user"` -} diff --git a/libpod/define/pod_inspect.go b/libpod/define/pod_inspect.go index 7f06e16fc..634cbb728 100644 --- a/libpod/define/pod_inspect.go +++ b/libpod/define/pod_inspect.go @@ -3,8 +3,6 @@ package define import ( "net" "time" - - "github.com/cri-o/ocicni/pkg/ocicni" ) // InspectPodData contains detailed information on a pod's configuration and @@ -60,7 +58,7 @@ type InspectPodData struct { type InspectPodInfraConfig struct { // PortBindings are ports that will be forwarded to the infra container // and then shared with the pod. - PortBindings []ocicni.PortMapping + PortBindings map[string][]InspectHostPort // HostNetwork is whether the infra container (and thus the whole pod) // will use the host's network and not create a network namespace. HostNetwork bool @@ -89,6 +87,8 @@ type InspectPodInfraConfig struct { // HostAdd adds a number of hosts to the infra container's resolv.conf // which will be shared with the rest of the pod. HostAdd []string + // Networks is a list of CNI networks te pod will join. + Networks []string } // InspectPodContainerInfo contains information on a container in a pod. diff --git a/libpod/events/events_linux.go b/libpod/events/events_linux.go index ffb100be8..482d7d6dd 100644 --- a/libpod/events/events_linux.go +++ b/libpod/events/events_linux.go @@ -8,20 +8,20 @@ import ( ) // NewEventer creates an eventer based on the eventer type -func NewEventer(options EventerOptions) (eventer Eventer, err error) { +func NewEventer(options EventerOptions) (Eventer, error) { logrus.Debugf("Initializing event backend %s", options.EventerType) switch strings.ToUpper(options.EventerType) { case strings.ToUpper(Journald.String()): - eventer, err = newEventJournalD(options) + eventer, err := newEventJournalD(options) if err != nil { return nil, errors.Wrapf(err, "eventer creation") } + return eventer, nil case strings.ToUpper(LogFile.String()): - eventer = EventLogFile{options} + return EventLogFile{options}, nil case strings.ToUpper(Null.String()): - eventer = NewNullEventer() + return NewNullEventer(), nil default: return nil, errors.Errorf("unknown event logger type: %s", strings.ToUpper(options.EventerType)) } - return eventer, nil } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 7985e17e1..1e79e8732 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -141,18 +141,18 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re } // Create and configure a new network namespace for a container -func (r *Runtime) createNetNS(ctr *Container) (n ns.NetNS, q []*cnitypes.Result, err error) { +func (r *Runtime) createNetNS(ctr *Container) (n ns.NetNS, q []*cnitypes.Result, retErr error) { ctrNS, err := netns.NewNS() if err != nil { return nil, nil, errors.Wrapf(err, "error creating network namespace for container %s", ctr.ID()) } defer func() { - if err != nil { - if err2 := netns.UnmountNS(ctrNS); err2 != nil { - logrus.Errorf("Error unmounting partially created network namespace for container %s: %v", ctr.ID(), err2) + if retErr != nil { + if err := netns.UnmountNS(ctrNS); err != nil { + logrus.Errorf("Error unmounting partially created network namespace for container %s: %v", ctr.ID(), err) } - if err2 := ctrNS.Close(); err2 != nil { - logrus.Errorf("Error closing partially created network namespace for container %s: %v", ctr.ID(), err2) + if err := ctrNS.Close(); err != nil { + logrus.Errorf("Error closing partially created network namespace for container %s: %v", ctr.ID(), err) } } }() @@ -188,7 +188,7 @@ func checkSlirpFlags(path string) (*slirpFeatures, error) { } // Configure the network namespace for a rootless container -func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { +func (r *Runtime) setupRootlessNetNS(ctr *Container) error { path := r.config.Engine.NetworkCmdPath if path == "" { @@ -342,7 +342,7 @@ func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout t return nil } -func (r *Runtime) setupRootlessPortMapping(ctr *Container, netnsPath string) (err error) { +func (r *Runtime) setupRootlessPortMapping(ctr *Container, netnsPath string) error { syncR, syncW, err := os.Pipe() if err != nil { return errors.Wrapf(err, "failed to open pipe") @@ -420,7 +420,7 @@ func (r *Runtime) setupRootlessPortMapping(ctr *Container, netnsPath string) (er } // Configure the network namespace using the container process -func (r *Runtime) setupNetNS(ctr *Container) (err error) { +func (r *Runtime) setupNetNS(ctr *Container) error { nsProcess := fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID) b := make([]byte, 16) @@ -587,21 +587,7 @@ func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) { // network. func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, error) { settings := new(define.InspectNetworkSettings) - settings.Ports = make(map[string][]define.InspectHostPort) - if c.config.PortMappings != nil { - for _, port := range c.config.PortMappings { - key := fmt.Sprintf("%d/%s", port.ContainerPort, port.Protocol) - mapping := settings.Ports[key] - if mapping == nil { - mapping = []define.InspectHostPort{} - } - mapping = append(mapping, define.InspectHostPort{ - HostIP: port.HostIP, - HostPort: fmt.Sprintf("%d", port.HostPort), - }) - settings.Ports[key] = mapping - } - } + settings.Ports = makeInspectPortBindings(c.config.PortMappings) // We can't do more if the network is down. if c.state.NetNS == nil { diff --git a/libpod/networking_unsupported.go b/libpod/networking_unsupported.go index d5189709c..69f470ff7 100644 --- a/libpod/networking_unsupported.go +++ b/libpod/networking_unsupported.go @@ -4,11 +4,11 @@ package libpod import "github.com/containers/libpod/v2/libpod/define" -func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { +func (r *Runtime) setupRootlessNetNS(ctr *Container) error { return define.ErrNotImplemented } -func (r *Runtime) setupNetNS(ctr *Container) (err error) { +func (r *Runtime) setupNetNS(ctr *Container) error { return define.ErrNotImplemented } @@ -16,7 +16,7 @@ func (r *Runtime) teardownNetNS(ctr *Container) error { return define.ErrNotImplemented } -func (r *Runtime) createNetNS(ctr *Container) (err error) { +func (r *Runtime) createNetNS(ctr *Container) error { return define.ErrNotImplemented } diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index e9d5cbaa3..e38646eec 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -190,7 +190,7 @@ func hasCurrentUserMapped(ctr *Container) bool { } // CreateContainer creates a container. -func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (err error) { +func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error { if !hasCurrentUserMapped(ctr) { for _, i := range []string{ctr.state.RunDir, ctr.runtime.config.Engine.TmpDir, ctr.config.StaticDir, ctr.state.Mountpoint, ctr.runtime.config.Engine.VolumePath} { if err := makeAccessible(i, ctr.RootUID(), ctr.RootGID()); err != nil { @@ -850,7 +850,7 @@ func (r *ConmonOCIRuntime) getLogTag(ctr *Container) (string, error) { } // createOCIContainer generates this container's main conmon instance and prepares it for starting -func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (err error) { +func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error { var stderrBuf bytes.Buffer runtimeDir, err := util.GetRuntimeDir() @@ -1297,8 +1297,9 @@ func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec return nil } -// newPipe creates a unix socket pair for communication -func newPipe() (parent *os.File, child *os.File, err error) { +// newPipe creates a unix socket pair for communication. +// Returns two files - first is parent, second is child. +func newPipe() (*os.File, *os.File, error) { fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_SEQPACKET|unix.SOCK_CLOEXEC, 0) if err != nil { return nil, nil, err diff --git a/libpod/options.go b/libpod/options.go index 3120a35d7..bff3f3c18 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1371,12 +1371,12 @@ func WithHealthCheck(healthCheck *manifest.Schema2HealthConfig) CtrCreateOption // WithCreateCommand adds the full command plus arguments of the current // process to the container config. -func WithCreateCommand() CtrCreateOption { +func WithCreateCommand(cmd []string) CtrCreateOption { return func(ctr *Container) error { if ctr.valid { return define.ErrCtrFinalized } - ctr.config.CreateCommand = os.Args + ctr.config.CreateCommand = cmd return nil } } @@ -1553,12 +1553,12 @@ func WithPodHostname(hostname string) PodCreateOption { // WithPodCreateCommand adds the full command plus arguments of the current // process to the pod config. -func WithPodCreateCommand() PodCreateOption { +func WithPodCreateCommand(createCmd []string) PodCreateOption { return func(pod *Pod) error { if pod.valid { return define.ErrPodFinalized } - pod.config.CreateCommand = os.Args + pod.config.CreateCommand = createCmd return nil } } diff --git a/libpod/pod_api.go b/libpod/pod_api.go index a02b171e1..f2ef81bec 100644 --- a/libpod/pod_api.go +++ b/libpod/pod_api.go @@ -481,6 +481,41 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) { } } + // Infra config contains detailed information on the pod's infra + // container. + var infraConfig *define.InspectPodInfraConfig + if p.config.InfraContainer != nil && p.config.InfraContainer.HasInfraContainer { + infraConfig = new(define.InspectPodInfraConfig) + infraConfig.HostNetwork = p.config.InfraContainer.HostNetwork + infraConfig.StaticIP = p.config.InfraContainer.StaticIP + infraConfig.StaticMAC = p.config.InfraContainer.StaticMAC + infraConfig.NoManageResolvConf = p.config.InfraContainer.UseImageResolvConf + infraConfig.NoManageHosts = p.config.InfraContainer.UseImageHosts + + if len(p.config.InfraContainer.DNSServer) > 0 { + infraConfig.DNSServer = make([]string, 0, len(p.config.InfraContainer.DNSServer)) + infraConfig.DNSServer = append(infraConfig.DNSServer, p.config.InfraContainer.DNSServer...) + } + if len(p.config.InfraContainer.DNSSearch) > 0 { + infraConfig.DNSSearch = make([]string, 0, len(p.config.InfraContainer.DNSSearch)) + infraConfig.DNSSearch = append(infraConfig.DNSSearch, p.config.InfraContainer.DNSSearch...) + } + if len(p.config.InfraContainer.DNSOption) > 0 { + infraConfig.DNSOption = make([]string, 0, len(p.config.InfraContainer.DNSOption)) + infraConfig.DNSOption = append(infraConfig.DNSOption, p.config.InfraContainer.DNSOption...) + } + if len(p.config.InfraContainer.HostAdd) > 0 { + infraConfig.HostAdd = make([]string, 0, len(p.config.InfraContainer.HostAdd)) + infraConfig.HostAdd = append(infraConfig.HostAdd, p.config.InfraContainer.HostAdd...) + } + if len(p.config.InfraContainer.Networks) > 0 { + infraConfig.Networks = make([]string, 0, len(p.config.InfraContainer.Networks)) + infraConfig.Networks = append(infraConfig.Networks, p.config.InfraContainer.Networks...) + } + + infraConfig.PortBindings = makeInspectPortBindings(p.config.InfraContainer.PortBindings) + } + inspectData := define.InspectPodData{ ID: p.ID(), Name: p.Name(), @@ -490,12 +525,12 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) { State: podState, Hostname: p.config.Hostname, Labels: p.Labels(), - CreateCgroup: false, + CreateCgroup: p.config.UsePodCgroup, CgroupParent: p.CgroupParent(), CgroupPath: p.state.CgroupPath, - CreateInfra: false, + CreateInfra: infraConfig != nil, InfraContainerID: p.state.InfraContainerID, - InfraConfig: nil, + InfraConfig: infraConfig, SharedNamespaces: sharesNS, NumContainers: uint(len(containers)), Containers: ctrs, diff --git a/libpod/runtime.go b/libpod/runtime.go index bf1e203c1..24370d50e 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -126,7 +126,7 @@ func SetXdgDirs() error { // NewRuntime creates a new container runtime // Options can be passed to override the default configuration for the runtime -func NewRuntime(ctx context.Context, options ...RuntimeOption) (runtime *Runtime, err error) { +func NewRuntime(ctx context.Context, options ...RuntimeOption) (*Runtime, error) { conf, err := config.NewConfig("") if err != nil { return nil, err @@ -140,13 +140,13 @@ func NewRuntime(ctx context.Context, options ...RuntimeOption) (runtime *Runtime // functions can be used to mutate this configuration further. // An error will be returned if the configuration file at the given path does // not exist or cannot be loaded -func NewRuntimeFromConfig(ctx context.Context, userConfig *config.Config, options ...RuntimeOption) (runtime *Runtime, err error) { +func NewRuntimeFromConfig(ctx context.Context, userConfig *config.Config, options ...RuntimeOption) (*Runtime, error) { return newRuntimeFromConfig(ctx, userConfig, options...) } -func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...RuntimeOption) (runtime *Runtime, err error) { - runtime = new(Runtime) +func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...RuntimeOption) (*Runtime, error) { + runtime := new(Runtime) if conf.Engine.OCIRuntime == "" { conf.Engine.OCIRuntime = "runc" @@ -236,7 +236,7 @@ func getLockManager(runtime *Runtime) (lock.Manager, error) { // Make a new runtime based on the given configuration // Sets up containers/storage, state store, OCI runtime -func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { +func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) { // Find a working conmon binary cPath, err := runtime.config.FindConmon() if err != nil { @@ -316,12 +316,11 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { return err } defer func() { - if err != nil && store != nil { + if retErr != nil && store != nil { // Don't forcibly shut down // We could be opening a store in use by another libpod - _, err2 := store.Shutdown(false) - if err2 != nil { - logrus.Errorf("Error removing store for partially-created runtime: %s", err2) + if _, err := store.Shutdown(false); err != nil { + logrus.Errorf("Error removing store for partially-created runtime: %s", err) } } }() diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index c073aabb5..e2bb696b0 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -34,7 +34,7 @@ type CtrCreateOption func(*Container) error type ContainerFilter func(*Container) bool // NewContainer creates a new container from a given OCI config. -func (r *Runtime) NewContainer(ctx context.Context, rSpec *spec.Spec, options ...CtrCreateOption) (c *Container, err error) { +func (r *Runtime) NewContainer(ctx context.Context, rSpec *spec.Spec, options ...CtrCreateOption) (*Container, error) { r.lock.Lock() defer r.lock.Unlock() if !r.valid { @@ -44,7 +44,7 @@ func (r *Runtime) NewContainer(ctx context.Context, rSpec *spec.Spec, options .. } // RestoreContainer re-creates a container from an imported checkpoint -func (r *Runtime) RestoreContainer(ctx context.Context, rSpec *spec.Spec, config *ContainerConfig) (c *Container, err error) { +func (r *Runtime) RestoreContainer(ctx context.Context, rSpec *spec.Spec, config *ContainerConfig) (*Container, error) { r.lock.Lock() defer r.lock.Unlock() if !r.valid { @@ -68,7 +68,7 @@ func (r *Runtime) RestoreContainer(ctx context.Context, rSpec *spec.Spec, config return r.setupContainer(ctx, ctr) } -func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConfig) (c *Container, err error) { +func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConfig) (*Container, error) { if rSpec == nil { return nil, errors.Wrapf(define.ErrInvalidArg, "must provide a valid runtime spec to create container") } @@ -122,7 +122,7 @@ func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConf return ctr, nil } -func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options ...CtrCreateOption) (c *Container, err error) { +func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options ...CtrCreateOption) (*Container, error) { span, _ := opentracing.StartSpanFromContext(ctx, "newContainer") span.SetTag("type", "runtime") defer span.Finish() @@ -141,7 +141,7 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. return r.setupContainer(ctx, ctr) } -func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Container, err error) { +func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Container, retErr error) { // Validate the container if err := ctr.validate(); err != nil { return nil, err @@ -157,9 +157,9 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai logrus.Debugf("Allocated lock %d for container %s", ctr.lock.ID(), ctr.ID()) defer func() { - if err != nil { - if err2 := ctr.lock.Free(); err2 != nil { - logrus.Errorf("Error freeing lock for container after creation failed: %v", err2) + if retErr != nil { + if err := ctr.lock.Free(); err != nil { + logrus.Errorf("Error freeing lock for container after creation failed: %v", err) } } }() @@ -272,9 +272,9 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai return nil, err } defer func() { - if err != nil { - if err2 := ctr.teardownStorage(); err2 != nil { - logrus.Errorf("Error removing partially-created container root filesystem: %s", err2) + if retErr != nil { + if err := ctr.teardownStorage(); err != nil { + logrus.Errorf("Error removing partially-created container root filesystem: %s", err) } } }() diff --git a/libpod/state_test.go b/libpod/state_test.go index 30ddf5e24..ef4f6f2be 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -34,13 +34,13 @@ var ( ) // Get an empty BoltDB state for use in tests -func getEmptyBoltState() (s State, p string, m lock.Manager, err error) { +func getEmptyBoltState() (_ State, _ string, _ lock.Manager, retErr error) { tmpDir, err := ioutil.TempDir("", tmpDirPrefix) if err != nil { return nil, "", nil, err } defer func() { - if err != nil { + if retErr != nil { os.RemoveAll(tmpDir) } }() @@ -66,13 +66,13 @@ func getEmptyBoltState() (s State, p string, m lock.Manager, err error) { } // Get an empty in-memory state for use in tests -func getEmptyInMemoryState() (s State, p string, m lock.Manager, err error) { +func getEmptyInMemoryState() (_ State, _ string, _ lock.Manager, retErr error) { tmpDir, err := ioutil.TempDir("", tmpDirPrefix) if err != nil { return nil, "", nil, err } defer func() { - if err != nil { + if retErr != nil { os.RemoveAll(tmpDir) } }() diff --git a/libpod/storage.go b/libpod/storage.go index be79b3fc0..e497d0daf 100644 --- a/libpod/storage.go +++ b/libpod/storage.go @@ -66,7 +66,7 @@ func (metadata *RuntimeContainerMetadata) SetMountLabel(mountLabel string) { // CreateContainerStorage creates the storage end of things. We already have the container spec created // TO-DO We should be passing in an Image object in the future. -func (r *storageService) CreateContainerStorage(ctx context.Context, systemContext *types.SystemContext, imageName, imageID, containerName, containerID string, options storage.ContainerOptions) (cinfo ContainerInfo, err error) { +func (r *storageService) CreateContainerStorage(ctx context.Context, systemContext *types.SystemContext, imageName, imageID, containerName, containerID string, options storage.ContainerOptions) (_ ContainerInfo, retErr error) { span, _ := opentracing.StartSpanFromContext(ctx, "createContainerStorage") span.SetTag("type", "storageService") defer span.Finish() @@ -132,9 +132,9 @@ func (r *storageService) CreateContainerStorage(ctx context.Context, systemConte // If anything fails after this point, we need to delete the incomplete // container before returning. defer func() { - if err != nil { - if err2 := r.store.DeleteContainer(container.ID); err2 != nil { - logrus.Infof("%v deleting partially-created container %q", err2, container.ID) + if retErr != nil { + if err := r.store.DeleteContainer(container.ID); err != nil { + logrus.Infof("%v deleting partially-created container %q", err, container.ID) return } diff --git a/libpod/util.go b/libpod/util.go index 7504295f0..8c2d946ba 100644 --- a/libpod/util.go +++ b/libpod/util.go @@ -15,6 +15,7 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/libpod/v2/libpod/define" "github.com/containers/libpod/v2/utils" + "github.com/cri-o/ocicni/pkg/ocicni" "github.com/fsnotify/fsnotify" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -254,3 +255,21 @@ func makeHTTPAttachHeader(stream byte, length uint32) []byte { binary.BigEndian.PutUint32(header[4:], length) return header } + +// Convert OCICNI port bindings into Inspect-formatted port bindings. +func makeInspectPortBindings(bindings []ocicni.PortMapping) map[string][]define.InspectHostPort { + portBindings := make(map[string][]define.InspectHostPort) + for _, port := range bindings { + key := fmt.Sprintf("%d/%s", port.ContainerPort, port.Protocol) + hostPorts := portBindings[key] + if hostPorts == nil { + hostPorts = []define.InspectHostPort{} + } + hostPorts = append(hostPorts, define.InspectHostPort{ + HostIP: port.HostIP, + HostPort: fmt.Sprintf("%d", port.HostPort), + }) + portBindings[key] = hostPorts + } + return portBindings +} diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go index 325f96b40..71586fca4 100644 --- a/pkg/api/handlers/compat/containers_attach.go +++ b/pkg/api/handlers/compat/containers_attach.go @@ -1,23 +1,22 @@ package compat import ( + "bufio" "fmt" + "io" + "net" "net/http" + "strings" "github.com/containers/libpod/v2/libpod" "github.com/containers/libpod/v2/libpod/define" "github.com/containers/libpod/v2/pkg/api/handlers/utils" + "github.com/containers/libpod/v2/pkg/api/server/idletracker" "github.com/gorilla/schema" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) -// AttachHeader is the literal header sent for upgraded/hijacked connections for -// attach, sourced from Docker at: -// https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go -// Using literally to ensure compatibility with existing clients. -const AttachHeader = "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n" - func AttachContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) @@ -98,21 +97,11 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { return } - // Hijack the connection - hijacker, ok := w.(http.Hijacker) - if !ok { - utils.InternalServerError(w, errors.Errorf("unable to hijack connection")) - return - } - - connection, buffer, err := hijacker.Hijack() + connection, buffer, err := AttachConnection(w, r) if err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection")) + utils.InternalServerError(w, err) return } - - fmt.Fprintf(connection, AttachHeader) - logrus.Debugf("Hijack for attach of container %s successful", ctr.ID()) // Perform HTTP attach. @@ -126,3 +115,57 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { logrus.Debugf("Attach for container %s completed successfully", ctr.ID()) } + +type HijackedConnection struct { + net.Conn // Connection + idleTracker *idletracker.IdleTracker // Connection tracker +} + +func (c HijackedConnection) Close() error { + logrus.Debugf("Hijacked connection closed") + + c.idleTracker.TrackHijackedClosed() + return c.Conn.Close() +} + +func AttachConnection(w http.ResponseWriter, r *http.Request) (net.Conn, *bufio.ReadWriter, error) { + idleTracker := r.Context().Value("idletracker").(*idletracker.IdleTracker) + + // Hijack the connection + hijacker, ok := w.(http.Hijacker) + if !ok { + return nil, nil, errors.Errorf("unable to hijack connection") + } + + connection, buffer, err := hijacker.Hijack() + if err != nil { + return nil, nil, errors.Wrapf(err, "error hijacking connection") + } + trackedConnection := HijackedConnection{ + Conn: connection, + idleTracker: idleTracker, + } + + WriteAttachHeaders(r, trackedConnection) + + return trackedConnection, buffer, nil +} + +func WriteAttachHeaders(r *http.Request, connection io.Writer) { + // AttachHeader is the literal header sent for upgraded/hijacked connections for + // attach, sourced from Docker at: + // https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go + // Using literally to ensure compatibility with existing clients. + c := r.Header.Get("Connection") + proto := r.Header.Get("Upgrade") + if len(proto) == 0 || !strings.EqualFold(c, "Upgrade") { + // OK - can't upgrade if not requested or protocol is not specified + fmt.Fprintf(connection, + "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") + } else { + // Upraded + fmt.Fprintf(connection, + "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: %s\r\n\r\n", + proto) + } +} diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 8034a529c..cbee8a8b6 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -1,6 +1,7 @@ package compat import ( + "context" "encoding/json" "fmt" "net/http" @@ -40,6 +41,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { } if len(input.HostConfig.Links) > 0 { utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter")) + return } newImage, err := runtime.ImageRuntime().NewFromLocal(input.Image) if err != nil { @@ -51,7 +53,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "GetConfig()")) return } - cc, err := makeCreateConfig(containerConfig, input, newImage) + cc, err := makeCreateConfig(r.Context(), containerConfig, input, newImage) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()")) return @@ -60,7 +62,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.CreateContainer(r.Context(), w, runtime, &cc) } -func makeCreateConfig(containerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { +func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { var ( err error init bool @@ -79,6 +81,22 @@ func makeCreateConfig(containerConfig *config.Config, input handlers.CreateConta workDir = input.WorkingDir } + if input.Entrypoint == nil { + entrypointSlice, err := newImage.Entrypoint(ctx) + if err != nil { + return createconfig.CreateConfig{}, err + } + input.Entrypoint = entrypointSlice + } + + if len(input.Cmd) == 0 { + cmdSlice, err := newImage.Cmd(ctx) + if err != nil { + return createconfig.CreateConfig{}, err + } + input.Cmd = cmdSlice + } + stopTimeout := containerConfig.Engine.StopTimeout if input.StopTimeout != nil { stopTimeout = uint(*input.StopTimeout) @@ -217,5 +235,16 @@ func makeCreateConfig(containerConfig *config.Config, input handlers.CreateConta Pid: pidConfig, } + + fullCmd := append(input.Entrypoint, input.Cmd...) + if len(fullCmd) > 0 { + m.PodmanPath = fullCmd[0] + if len(fullCmd) == 1 { + m.Args = fullCmd + } else { + m.Args = fullCmd[1:] + } + } + return m, nil } diff --git a/pkg/api/handlers/compat/containers_logs.go b/pkg/api/handlers/compat/containers_logs.go index 8147f4d38..30ee030e8 100644 --- a/pkg/api/handlers/compat/containers_logs.go +++ b/pkg/api/handlers/compat/containers_logs.go @@ -92,7 +92,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) { options.WaitGroup = &wg logChannel := make(chan *logs.LogLine, tail+1) - if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil { + if err := runtime.Log(r.Context(), []*libpod.Container{ctnr}, options, logChannel); err != nil { utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name)) return } @@ -105,50 +105,48 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) { var frame strings.Builder header := make([]byte, 8) - for ok := true; ok; ok = query.Follow { - for line := range logChannel { - if _, found := r.URL.Query()["until"]; found { - if line.Time.After(until) { - break - } + for line := range logChannel { + if _, found := r.URL.Query()["until"]; found { + if line.Time.After(until) { + break } + } - // Reset buffer we're ready to loop again - frame.Reset() - switch line.Device { - case "stdout": - if !query.Stdout { - continue - } - header[0] = 1 - case "stderr": - if !query.Stderr { - continue - } - header[0] = 2 - default: - // Logging and moving on is the best we can do here. We may have already sent - // a Status and Content-Type to client therefore we can no longer report an error. - log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID()) + // Reset buffer we're ready to loop again + frame.Reset() + switch line.Device { + case "stdout": + if !query.Stdout { continue } - - if query.Timestamps { - frame.WriteString(line.Time.Format(time.RFC3339)) - frame.WriteString(" ") + header[0] = 1 + case "stderr": + if !query.Stderr { + continue } - frame.WriteString(line.Msg) + header[0] = 2 + default: + // Logging and moving on is the best we can do here. We may have already sent + // a Status and Content-Type to client therefore we can no longer report an error. + log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID()) + continue + } - binary.BigEndian.PutUint32(header[4:], uint32(frame.Len())) - if _, err := w.Write(header[0:8]); err != nil { - log.Errorf("unable to write log output header: %q", err) - } - if _, err := io.WriteString(w, frame.String()); err != nil { - log.Errorf("unable to write frame string: %q", err) - } - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() - } + if query.Timestamps { + frame.WriteString(line.Time.Format(time.RFC3339)) + frame.WriteString(" ") + } + frame.WriteString(line.Msg) + + binary.BigEndian.PutUint32(header[4:], uint32(frame.Len())) + if _, err := w.Write(header[0:8]); err != nil { + log.Errorf("unable to write log output header: %q", err) + } + if _, err := io.WriteString(w, frame.String()); err != nil { + log.Errorf("unable to write frame string: %q", err) + } + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() } } } diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go index aee4196dd..a3b8cb573 100644 --- a/pkg/api/handlers/compat/exec.go +++ b/pkg/api/handlers/compat/exec.go @@ -173,21 +173,11 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { return } - // Hijack the connection - hijacker, ok := w.(http.Hijacker) - if !ok { - utils.InternalServerError(w, errors.Errorf("unable to hijack connection")) - return - } - - connection, buffer, err := hijacker.Hijack() + connection, buffer, err := AttachConnection(w, r) if err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection")) + utils.InternalServerError(w, err) return } - - fmt.Fprintf(connection, AttachHeader) - logrus.Debugf("Hijack for attach of container %s exec session %s successful", sessionCtr.ID(), sessionID) if err := sessionCtr.ExecHTTPStartAndAttach(sessionID, connection, buffer, nil, nil, nil); err != nil { diff --git a/pkg/api/server/handler_api.go b/pkg/api/server/handler_api.go index b0fd932ba..53fe8952b 100644 --- a/pkg/api/server/handler_api.go +++ b/pkg/api/server/handler_api.go @@ -37,6 +37,7 @@ func (s *APIServer) APIHandler(h http.HandlerFunc) http.HandlerFunc { c := context.WithValue(r.Context(), "decoder", s.Decoder) //nolint c = context.WithValue(c, "runtime", s.Runtime) //nolint c = context.WithValue(c, "shutdownFunc", s.Shutdown) //nolint + c = context.WithValue(c, "idletracker", s.idleTracker) //nolint r = r.WithContext(c) h(w, r) diff --git a/pkg/api/server/idletracker/idletracker.go b/pkg/api/server/idletracker/idletracker.go new file mode 100644 index 000000000..1ee905a99 --- /dev/null +++ b/pkg/api/server/idletracker/idletracker.go @@ -0,0 +1,74 @@ +package idletracker + +import ( + "net" + "net/http" + "sync" + "time" + + "github.com/sirupsen/logrus" +) + +type IdleTracker struct { + http map[net.Conn]struct{} + hijacked int + total int + mux sync.Mutex + timer *time.Timer + Duration time.Duration +} + +func NewIdleTracker(idle time.Duration) *IdleTracker { + return &IdleTracker{ + http: make(map[net.Conn]struct{}), + Duration: idle, + timer: time.NewTimer(idle), + } +} + +func (t *IdleTracker) ConnState(conn net.Conn, state http.ConnState) { + t.mux.Lock() + defer t.mux.Unlock() + + oldActive := t.ActiveConnections() + logrus.Debugf("IdleTracker %p:%v %d/%d connection(s)", conn, state, oldActive, t.TotalConnections()) + switch state { + case http.StateNew, http.StateActive: + t.http[conn] = struct{}{} + // stop the timer if we transitioned from idle + if oldActive == 0 { + t.timer.Stop() + } + t.total++ + case http.StateHijacked: + // hijacked connections are handled elsewhere + delete(t.http, conn) + t.hijacked++ + case http.StateIdle, http.StateClosed: + delete(t.http, conn) + // Restart the timer if we've become idle + if oldActive > 0 && len(t.http) == 0 { + t.timer.Stop() + t.timer.Reset(t.Duration) + } + } +} + +func (t *IdleTracker) TrackHijackedClosed() { + t.mux.Lock() + defer t.mux.Unlock() + + t.hijacked-- +} + +func (t *IdleTracker) ActiveConnections() int { + return len(t.http) + t.hijacked +} + +func (t *IdleTracker) TotalConnections() int { + return t.total +} + +func (t *IdleTracker) Done() <-chan time.Time { + return t.timer.C +} diff --git a/pkg/api/server/register_generate.go b/pkg/api/server/register_generate.go index 82f1dc680..a1ab3f727 100644 --- a/pkg/api/server/register_generate.go +++ b/pkg/api/server/register_generate.go @@ -13,8 +13,8 @@ func (s *APIServer) registerGenerateHandlers(r *mux.Router) error { // tags: // - containers // - pods - // summary: Play a Kubernetes YAML file. - // description: Create and run pods based on a Kubernetes YAML file (pod or service kind). + // summary: Generate a Kubernetes YAML file. + // description: Generate Kubernetes YAML based on a pod or container. // parameters: // - in: path // name: name:.* diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 8af6d3186..1c6007745 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -10,12 +10,12 @@ import ( "runtime" goRuntime "runtime" "strings" - "sync" "syscall" "time" "github.com/containers/libpod/v2/libpod" "github.com/containers/libpod/v2/pkg/api/handlers" + "github.com/containers/libpod/v2/pkg/api/server/idletracker" "github.com/coreos/go-systemd/v22/activation" "github.com/gorilla/mux" "github.com/gorilla/schema" @@ -24,14 +24,14 @@ import ( ) type APIServer struct { - http.Server // The HTTP work happens here - *schema.Decoder // Decoder for Query parameters to structs - context.Context // Context to carry objects to handlers - *libpod.Runtime // Where the real work happens - net.Listener // mux for routing HTTP API calls to libpod routines - context.CancelFunc // Stop APIServer - idleTracker *IdleTracker // Track connections to support idle shutdown - pprof *http.Server // Sidecar http server for providing performance data + http.Server // The HTTP work happens here + *schema.Decoder // Decoder for Query parameters to structs + context.Context // Context to carry objects to handlers + *libpod.Runtime // Where the real work happens + net.Listener // mux for routing HTTP API calls to libpod routines + context.CancelFunc // Stop APIServer + idleTracker *idletracker.IdleTracker // Track connections to support idle shutdown + pprof *http.Server // Sidecar http server for providing performance data } // Number of seconds to wait for next request, if exceeded shutdown server @@ -68,7 +68,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li } router := mux.NewRouter().UseEncodedPath() - idle := NewIdleTracker(duration) + idle := idletracker.NewIdleTracker(duration) server := APIServer{ Server: http.Server{ @@ -231,55 +231,3 @@ func (s *APIServer) Shutdown() error { func (s *APIServer) Close() error { return s.Server.Close() } - -type IdleTracker struct { - active map[net.Conn]struct{} - total int - mux sync.Mutex - timer *time.Timer - Duration time.Duration -} - -func NewIdleTracker(idle time.Duration) *IdleTracker { - return &IdleTracker{ - active: make(map[net.Conn]struct{}), - Duration: idle, - timer: time.NewTimer(idle), - } -} - -func (t *IdleTracker) ConnState(conn net.Conn, state http.ConnState) { - t.mux.Lock() - defer t.mux.Unlock() - - oldActive := len(t.active) - logrus.Debugf("IdleTracker %p:%v %d/%d connection(s)", conn, state, t.ActiveConnections(), t.TotalConnections()) - switch state { - case http.StateNew, http.StateActive, http.StateHijacked: - t.active[conn] = struct{}{} - // stop the timer if we transitioned from idle - if oldActive == 0 { - t.timer.Stop() - } - t.total++ - case http.StateIdle, http.StateClosed: - delete(t.active, conn) - // Restart the timer if we've become idle - if oldActive > 0 && len(t.active) == 0 { - t.timer.Stop() - t.timer.Reset(t.Duration) - } - } -} - -func (t *IdleTracker) ActiveConnections() int { - return len(t.active) -} - -func (t *IdleTracker) TotalConnections() int { - return t.total -} - -func (t *IdleTracker) Done() <-chan time.Time { - return t.timer.C -} diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go index 077bb244f..297563688 100644 --- a/pkg/bindings/containers/attach.go +++ b/pkg/bindings/containers/attach.go @@ -457,15 +457,15 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, streams *define.A switch { case fd == 0: - if streams.AttachOutput { + if streams.AttachInput { + // Write STDIN to STDOUT (echoing characters + // typed by another attach session) if _, err := streams.OutputStream.Write(frame[0:l]); err != nil { return err } } case fd == 1: - if streams.AttachInput { - // Write STDIN to STDOUT (echoing characters - // typed by another attach session) + if streams.AttachOutput { if _, err := streams.OutputStream.Write(frame[0:l]); err != nil { return err } diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index 8023034ef..9e9b834ef 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -104,6 +104,7 @@ type PodRmReport struct { type PodCreateOptions struct { CGroupParent string + CreateCommand []string Hostname string Infra bool InfraImage string @@ -133,6 +134,7 @@ func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) { } s.InfraImage = p.InfraImage s.SharedNamespaces = p.Share + s.PodCreateCommand = p.CreateCommand // Networking config s.NetNS = p.Net.Network diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 596fc2cc1..8909f831d 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -924,7 +924,7 @@ func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []strin } logChannel := make(chan *logs.LogLine, chSize) - if err := ic.Libpod.Log(ctrs, logOpts, logChannel); err != nil { + if err := ic.Libpod.Log(ctx, ctrs, logOpts, logChannel); err != nil { return err } diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index f82da2c95..888811958 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -453,11 +453,16 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container containerConfig.Command = []string{} if imageData != nil && imageData.Config != nil { - containerConfig.Command = append(containerConfig.Command, imageData.Config.Entrypoint...) + containerConfig.Command = imageData.Config.Entrypoint } if len(containerYAML.Command) != 0 { - containerConfig.Command = append(containerConfig.Command, containerYAML.Command...) - } else if imageData != nil && imageData.Config != nil { + containerConfig.Command = containerYAML.Command + } + // doc https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#notes + if len(containerYAML.Args) != 0 { + containerConfig.Command = append(containerConfig.Command, containerYAML.Args...) + } else if len(containerYAML.Command) == 0 { + // Add the Cmd from the image config only if containerYAML.Command and containerYAML.Args are empty containerConfig.Command = append(containerConfig.Command, imageData.Config.Cmd...) } if imageData != nil && len(containerConfig.Command) == 0 { diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 855ca7ff9..435902ded 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -8,7 +8,6 @@ import ( "os/exec" "path/filepath" "strconv" - "syscall" "github.com/containers/common/pkg/config" "github.com/containers/libpod/v2/libpod/define" @@ -146,27 +145,6 @@ func movePauseProcessToScope() error { return utils.RunUnderSystemdScope(int(pid), "user.slice", "podman-pause.scope") } -func setRLimits() error { // nolint:deadcode,unused - rlimits := new(syscall.Rlimit) - rlimits.Cur = 1048576 - rlimits.Max = 1048576 - if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - return errors.Wrapf(err, "error getting rlimits") - } - rlimits.Cur = rlimits.Max - if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - return errors.Wrapf(err, "error setting new rlimits") - } - } - return nil -} - -func setUMask() { // nolint:deadcode,unused - // Be sure we can create directories with 0755 mode. - syscall.Umask(0022) -} - // checkInput can be used to verify any of the globalopt values func checkInput() error { // nolint:deadcode,unused return nil @@ -252,13 +230,18 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System } } - named, err := reference.ParseNormalizedNamed(name) - if err != nil { - return nil, err - } - repository = named.Name() - if tagged, isTagged := named.(reference.NamedTagged); isTagged { - tag = tagged.Tag() + if len(name) > 0 { + named, err := reference.ParseNormalizedNamed(name) + if err != nil { + return nil, err + } + repository = named.Name() + if tagged, isTagged := named.(reference.NamedTagged); isTagged { + tag = tagged.Tag() + } + } else { + repository = "<none>" + tag = "<none>" } report := entities.SystemDfImageReport{ diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 879c66895..55c3238d2 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -406,10 +406,6 @@ func CreateContainerFromCreateConfig(ctx context.Context, r *libpod.Runtime, cre return nil, err } - // Set the CreateCommand explicitly. Some (future) consumers of libpod - // might not want to set it. - options = append(options, libpod.WithCreateCommand()) - ctr, err := r.NewContainer(ctx, runtimeSpec, options...) if err != nil { return nil, err diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 6f52b88b1..b974772d5 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -505,10 +505,9 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate. func addRlimits(config *CreateConfig, g *generate.Generator) error { var ( - kernelMax uint64 = 1048576 - isRootless = rootless.IsRootless() - nofileSet = false - nprocSet = false + isRootless = rootless.IsRootless() + nofileSet = false + nprocSet = false ) for _, u := range config.Resources.Ulimit { @@ -538,8 +537,8 @@ func addRlimits(config *CreateConfig, g *generate.Generator) error { // files and number of processes to the maximum they can be set to // (without overriding a sysctl) if !nofileSet { - max := kernelMax - current := kernelMax + max := define.RLimitDefaultValue + current := define.RLimitDefaultValue if isRootless { var rlimit unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err != nil { @@ -555,8 +554,8 @@ func addRlimits(config *CreateConfig, g *generate.Generator) error { g.AddProcessRlimits("RLIMIT_NOFILE", max, current) } if !nprocSet { - max := kernelMax - current := kernelMax + max := define.RLimitDefaultValue + current := define.RLimitDefaultValue if isRootless { var rlimit unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err != nil { diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go index 8063bee38..57dd2aba7 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -25,6 +25,15 @@ func exclusiveOptions(opt1, opt2 string) error { // input for creating a container. func (s *SpecGenerator) Validate() error { + if rootless.IsRootless() { + if s.StaticIP != nil || s.StaticIPv6 != nil { + return ErrNoStaticIPRootless + } + if s.StaticMAC != nil { + return ErrNoStaticMACRootless + } + } + // // ContainerBasicConfig // @@ -65,10 +74,6 @@ func (s *SpecGenerator) Validate() error { if len(s.CapAdd) > 0 && s.Privileged { return exclusiveOptions("CapAdd", "privileged") } - // apparmor and privileged are exclusive - if len(s.ApparmorProfile) > 0 && s.Privileged { - return exclusiveOptions("AppArmorProfile", "privileged") - } // userns and idmappings conflict if s.UserNS.IsPrivate() && s.IDMappings == nil { return errors.Wrap(ErrInvalidSpecConfig, "IDMappings are required when not creating a User namespace") diff --git a/pkg/specgen/generate/config_linux.go b/pkg/specgen/generate/config_linux.go index 5d928cc5d..e445e6f0c 100644 --- a/pkg/specgen/generate/config_linux.go +++ b/pkg/specgen/generate/config_linux.go @@ -161,6 +161,7 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate. "/proc/scsi", "/sys/firmware", "/sys/fs/selinux", + "/sys/dev", } { g.AddLinuxMaskedPaths(mp) } diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 57eaff355..be1e3b48e 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -78,7 +78,9 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener } options := []libpod.CtrCreateOption{} - options = append(options, libpod.WithCreateCommand()) + if s.ContainerCreateCommand != nil { + options = append(options, libpod.WithCreateCommand(s.ContainerCreateCommand)) + } var newImage *image.Image if s.Rootfs != "" { @@ -104,7 +106,12 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener return nil, err } - opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, newImage) + command, err := makeCommand(ctx, s, newImage, rtc) + if err != nil { + return nil, err + } + + opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, newImage, command) if err != nil { return nil, err } @@ -116,14 +123,14 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener } options = append(options, libpod.WithExitCommand(exitCommandArgs)) - runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts, pod) + runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts, pod, command) if err != nil { return nil, err } return rt.NewContainer(ctx, runtimeSpec, options...) } -func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, img *image.Image) ([]libpod.CtrCreateOption, error) { +func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, img *image.Image, command []string) ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption var err error @@ -138,7 +145,6 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. case "false": break case "", "true": - command := s.Command if len(command) == 0 { command, err = img.Cmd(ctx) if err != nil { diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 0a485e7cd..f279aac1c 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -20,10 +20,9 @@ import ( func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { var ( - kernelMax uint64 = 1048576 - isRootless = rootless.IsRootless() - nofileSet = false - nprocSet = false + isRootless = rootless.IsRootless() + nofileSet = false + nprocSet = false ) if s.Rlimits == nil { @@ -45,8 +44,8 @@ func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { // files and number of processes to the maximum they can be set to // (without overriding a sysctl) if !nofileSet { - max := kernelMax - current := kernelMax + max := define.RLimitDefaultValue + current := define.RLimitDefaultValue if isRootless { var rlimit unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err != nil { @@ -62,8 +61,8 @@ func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { g.AddProcessRlimits("RLIMIT_NOFILE", max, current) } if !nprocSet { - max := kernelMax - current := kernelMax + max := define.RLimitDefaultValue + current := define.RLimitDefaultValue if isRootless { var rlimit unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err != nil { @@ -87,7 +86,7 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image finalCommand := []string{} entrypoint := s.Entrypoint - if len(entrypoint) == 0 && img != nil { + if entrypoint == nil && img != nil { newEntry, err := img.Entrypoint(ctx) if err != nil { return nil, err @@ -126,7 +125,7 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image return finalCommand, nil } -func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *image.Image, mounts []spec.Mount, pod *libpod.Pod) (*spec.Spec, error) { +func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *image.Image, mounts []spec.Mount, pod *libpod.Pod, finalCmd []string) (*spec.Spec, error) { var ( inUserNS bool ) @@ -252,10 +251,6 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } g.SetProcessCwd(s.WorkDir) - finalCmd, err := makeCommand(ctx, s, newImage, rtc) - if err != nil { - return nil, err - } g.SetProcessArgs(finalCmd) g.SetProcessTerminal(s.Terminal) @@ -290,13 +285,6 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } } - // SECURITY OPTS - g.SetProcessNoNewPrivileges(s.NoNewPrivileges) - - if !s.Privileged { - g.SetProcessApparmorProfile(s.ApparmorProfile) - } - BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), &g) for name, val := range s.Env { diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index 690651a23..4fe1b6435 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -93,7 +93,9 @@ func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, er options = append(options, libpod.WithInfraContainerPorts(ports)) } options = append(options, libpod.WithPodCgroups()) - options = append(options, libpod.WithPodCreateCommand()) + if p.PodCreateCommand != nil { + options = append(options, libpod.WithPodCreateCommand(p.PodCreateCommand)) + } if len(p.InfraConmonPidFile) > 0 { options = append(options, libpod.WithInfraConmonPidFile(p.InfraConmonPidFile)) } diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go index 9412ecfbf..c8d1c27c5 100644 --- a/pkg/specgen/generate/ports.go +++ b/pkg/specgen/generate/ports.go @@ -356,6 +356,7 @@ func checkProtocol(protocol string, allowSCTP bool) ([]string, error) { splitProto := strings.Split(protocol, ",") // Don't error on duplicates - just deduplicate for _, p := range splitProto { + p = strings.ToLower(p) switch p { case protoTCP, "": protocols[protoTCP] = struct{}{} diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index 70493cd5f..fcd1622f9 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -3,6 +3,7 @@ package generate import ( "strings" + "github.com/containers/common/pkg/apparmor" "github.com/containers/common/pkg/capabilities" "github.com/containers/common/pkg/config" "github.com/containers/libpod/v2/libpod" @@ -56,6 +57,28 @@ func setLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig s return nil } +func setupApparmor(s *specgen.SpecGenerator, rtc *config.Config, g *generate.Generator) error { + hasProfile := len(s.ApparmorProfile) > 0 + if !apparmor.IsEnabled() { + if hasProfile { + return errors.Errorf("Apparmor profile %q specified, but Apparmor is not enabled on this system", s.ApparmorProfile) + } + return nil + } + // If privileged and caller did not specify apparmor profiles return + if s.Privileged && !hasProfile { + return nil + } + if !hasProfile { + s.ApparmorProfile = rtc.Containers.ApparmorProfile + } + if len(s.ApparmorProfile) > 0 { + g.SetProcessApparmorProfile(s.ApparmorProfile) + } + + return nil +} + func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image, rtc *config.Config) error { var ( caplist []string @@ -105,6 +128,13 @@ func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, } } } + + g.SetProcessNoNewPrivileges(s.NoNewPrivileges) + + if err := setupApparmor(s, rtc, g); err != nil { + return err + } + configSpec := g.Config configSpec.Process.Capabilities.Bounding = caplist diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go index 070bb1e41..69c3b58ed 100644 --- a/pkg/specgen/pod_validate.go +++ b/pkg/specgen/pod_validate.go @@ -1,6 +1,7 @@ package specgen import ( + "github.com/containers/libpod/v2/pkg/rootless" "github.com/containers/libpod/v2/pkg/util" "github.com/pkg/errors" ) @@ -18,6 +19,16 @@ func exclusivePodOptions(opt1, opt2 string) error { // Validate verifies the input is valid func (p *PodSpecGenerator) Validate() error { + + if rootless.IsRootless() { + if p.StaticIP != nil { + return ErrNoStaticIPRootless + } + if p.StaticMAC != nil { + return ErrNoStaticMACRootless + } + } + // PodBasicConfig if p.NoInfra { if len(p.InfraCommand) > 0 { diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go index 600d27004..3c32ec365 100644 --- a/pkg/specgen/podspecgen.go +++ b/pkg/specgen/podspecgen.go @@ -49,6 +49,12 @@ type PodBasicConfig struct { // Conflicts with NoInfra=true. // Optional. SharedNamespaces []string `json:"shared_namespaces,omitempty"` + // PodCreateCommand is the command used to create this pod. + // This will be shown in the output of Inspect() on the pod, and may + // also be used by some tools that wish to recreate the pod + // (e.g. `podman generate systemd --new`). + // Optional. + PodCreateCommand []string `json:"pod_create_command,omitempty"` } // PodNetworkConfig contains networking configuration for a pod. diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 327c15c5a..bd738f5a7 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -1,6 +1,7 @@ package specgen import ( + "errors" "net" "syscall" @@ -130,6 +131,13 @@ type ContainerBasicConfig struct { // Remove indicates if the container should be removed once it has been started // and exits Remove bool `json:"remove,omitempty"` + // ContainerCreateCommand is the command that was used to create this + // container. + // This will be shown in the output of Inspect() on the container, and + // may also be used by some tools that wish to recreate the container + // (e.g. `podman generate systemd --new`). + // Optional. + ContainerCreateCommand []string `json:"containerCreateCommand,omitempty"` } // ContainerStorageConfig contains information on the storage configuration of a @@ -449,6 +457,15 @@ type PortMapping struct { Protocol string `json:"protocol,omitempty"` } +var ( + // ErrNoStaticIPRootless is used when a rootless user requests to assign a static IP address + // to a pod or container + ErrNoStaticIPRootless error = errors.New("rootless containers and pods cannot be assigned static IP addresses") + // ErrNoStaticMACRootless is used when a rootless user requests to assign a static MAC address + // to a pod or container + ErrNoStaticMACRootless error = errors.New("rootless containers and pods cannot be assigned static MAC addresses") +) + // NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator { csc := ContainerStorageConfig{} diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 47d3e231d..9eeb116c0 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -555,7 +555,7 @@ func ValidatePullType(pullType string) (PullType, error) { switch pullType { case "always": return PullImageAlways, nil - case "missing": + case "missing", "IfNotPresent": return PullImageMissing, nil case "never": return PullImageNever, nil @@ -641,7 +641,7 @@ func ValidateSysctls(strSlice []string) (map[string]string, error) { } } if !foundMatch { - return nil, errors.Errorf("sysctl '%s' is not whitelisted", arr[0]) + return nil, errors.Errorf("sysctl '%s' is not allowed", arr[0]) } } return sysctl, nil diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 8650ba000..07b492331 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -754,7 +754,7 @@ func (i *VarlinkAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string if err != nil { return call.ReplyErrorOccurred(err.Error()) } - if err := i.Runtime.Log(containers, &options, logChannel); err != nil { + if err := i.Runtime.Log(getContext(), containers, &options, logChannel); err != nil { return err } go func() { diff --git a/pkg/varlinkapi/create.go b/pkg/varlinkapi/create.go index 5c5f075f7..ac93939d9 100644 --- a/pkg/varlinkapi/create.go +++ b/pkg/varlinkapi/create.go @@ -915,10 +915,6 @@ func CreateContainerFromCreateConfig(ctx context.Context, r *libpod.Runtime, cre return nil, err } - // Set the CreateCommand explicitly. Some (future) consumers of libpod - // might not want to set it. - options = append(options, libpod.WithCreateCommand()) - ctr, err := r.NewContainer(ctx, runtimeSpec, options...) if err != nil { return nil, err diff --git a/rootless.md b/rootless.md index e5b71f0b9..e6f17a370 100644 --- a/rootless.md +++ b/rootless.md @@ -30,7 +30,7 @@ can easily fail * Only other supported driver is VFS. * No CNI Support * CNI wants to modify IPTables, plus other network manipulation that requires CAP_SYS_ADMIN. - * There is potential we could probably do some sort of blacklisting of the relevant plugins, and add a new plugin for rootless networking - slirp4netns as one example and there may be others + * There is potential we could probably do some sort of denylisting of the relevant plugins, and add a new plugin for rootless networking - slirp4netns as one example and there may be others * Cannot use ping out of the box. * [(Can be fixed by setting sysctl on host)](https://github.com/containers/libpod/blob/master/troubleshooting.md#6-rootless-containers-cannot-ping-hosts) * Requires new shadow-utils (not found in older (RHEL7/Centos7 distros) Should be fixed in RHEL7.7 release) diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 6633f3a53..acd28170b 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -591,3 +591,7 @@ func SkipIfNotFedora() { ginkgo.Skip("Test can only run on Fedora") } } + +func isRootless() bool { + return os.Geteuid() != 0 +} diff --git a/test/e2e/create_staticip_test.go b/test/e2e/create_staticip_test.go index 995193a7d..261792a39 100644 --- a/test/e2e/create_staticip_test.go +++ b/test/e2e/create_staticip_test.go @@ -6,6 +6,7 @@ import ( "os" "time" + "github.com/containers/libpod/v2/pkg/rootless" . "github.com/containers/libpod/v2/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -19,7 +20,6 @@ var _ = Describe("Podman create with --ip flag", func() { ) BeforeEach(func() { - SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) @@ -39,18 +39,21 @@ var _ = Describe("Podman create with --ip flag", func() { }) It("Podman create --ip with garbage address", func() { + SkipIfRootless() result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "114232346", ALPINE, "ls"}) result.WaitWithDefaultTimeout() Expect(result).To(ExitWithError()) }) It("Podman create --ip with v6 address", func() { + SkipIfRootless() result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "2001:db8:bad:beef::1", ALPINE, "ls"}) result.WaitWithDefaultTimeout() Expect(result).To(ExitWithError()) }) It("Podman create --ip with non-allocatable IP", func() { + SkipIfRootless() result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "203.0.113.124", ALPINE, "ls"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) @@ -64,19 +67,25 @@ var _ = Describe("Podman create with --ip flag", func() { ip := GetRandomIPAddress() result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", ip, ALPINE, "ip", "addr"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Equal(0)) + // Rootless static ip assignment should error + if rootless.IsRootless() { + Expect(result.ExitCode()).To(Equal(125)) + } else { + Expect(result.ExitCode()).To(Equal(0)) - result = podmanTest.Podman([]string{"start", "test"}) - result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Equal(0)) + result = podmanTest.Podman([]string{"start", "test"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) - result = podmanTest.Podman([]string{"logs", "test"}) - result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Equal(0)) - Expect(result.OutputToString()).To(ContainSubstring(ip + "/16")) + result = podmanTest.Podman([]string{"logs", "test"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(result.OutputToString()).To(ContainSubstring(ip + "/16")) + } }) It("Podman create two containers with the same IP", func() { + SkipIfRootless() ip := GetRandomIPAddress() result := podmanTest.Podman([]string{"create", "--name", "test1", "--ip", ip, ALPINE, "sleep", "999"}) result.WaitWithDefaultTimeout() diff --git a/test/e2e/create_staticmac_test.go b/test/e2e/create_staticmac_test.go index 93af5ab10..33675d607 100644 --- a/test/e2e/create_staticmac_test.go +++ b/test/e2e/create_staticmac_test.go @@ -1,10 +1,9 @@ -// +build !remoteclient - package integration import ( "os" + "github.com/containers/libpod/v2/pkg/rootless" . "github.com/containers/libpod/v2/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -18,7 +17,6 @@ var _ = Describe("Podman run with --mac-address flag", func() { ) BeforeEach(func() { - SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) @@ -40,7 +38,11 @@ var _ = Describe("Podman run with --mac-address flag", func() { It("Podman run --mac-address", func() { result := podmanTest.Podman([]string{"run", "--mac-address", "92:d0:c6:0a:29:34", ALPINE, "ip", "addr"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Equal(0)) - Expect(result.OutputToString()).To(ContainSubstring("92:d0:c6:0a:29:34")) + if rootless.IsRootless() { + Expect(result.ExitCode()).To(Equal(125)) + } else { + Expect(result.ExitCode()).To(Equal(0)) + Expect(result.OutputToString()).To(ContainSubstring("92:d0:c6:0a:29:34")) + } }) }) diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index 5a519413e..736376207 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -98,7 +98,6 @@ var _ = Describe("Podman exec", func() { It("podman exec os.Setenv env", func() { // remote doesn't properly interpret os.Setenv - SkipIfRemote() setup := podmanTest.RunTopContainer("test1") setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go index e38ace53f..8aa9712fd 100644 --- a/test/e2e/info_test.go +++ b/test/e2e/info_test.go @@ -11,6 +11,7 @@ import ( . "github.com/containers/libpod/v2/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gexec" ) var _ = Describe("Podman Info", func() { @@ -35,11 +36,30 @@ var _ = Describe("Podman Info", func() { processTestResult(f) }) - It("podman info json output", func() { - session := podmanTest.Podman([]string{"info", "--format=json"}) - session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(0)) - + It("podman info --format json", func() { + tests := []struct { + input string + success bool + exitCode int + }{ + {"json", true, 0}, + {" json", true, 0}, + {"json ", true, 0}, + {" json ", true, 0}, + {"{{json .}}", true, 0}, + {"{{ json .}}", true, 0}, + {"{{json . }}", true, 0}, + {" {{ json . }} ", true, 0}, + {"{{json }}", false, 125}, + {"{{json .", false, 125}, + {"json . }}", false, 0}, // Note: this does NOT fail but produces garbage + } + for _, tt := range tests { + session := podmanTest.Podman([]string{"info", "--format", tt.input}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(tt.exitCode)) + Expect(session.IsJSONOutputValid()).To(Equal(tt.success)) + } }) It("podman info --format GO template", func() { diff --git a/test/e2e/libpod_suite_remote_test.go b/test/e2e/libpod_suite_remote_test.go index cb1bae16d..ad0f8023b 100644 --- a/test/e2e/libpod_suite_remote_test.go +++ b/test/e2e/libpod_suite_remote_test.go @@ -28,11 +28,6 @@ func SkipIfRootless() { ginkgo.Skip("This function is not enabled for rootless podman") } } -func SkipIfRootlessV2() { - if os.Geteuid() != 0 { - ginkgo.Skip("This function is not enabled for v2 rootless podman") - } -} // Podman is the exec call to podman on the filesystem func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration { diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 63d5eff21..b1844b917 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "text/template" . "github.com/containers/libpod/v2/test/utils" @@ -50,6 +51,10 @@ spec: {{ range .Cmd }} - {{.}} {{ end }} + args: + {{ range .Arg }} + - {{.}} + {{ end }} env: - name: PATH value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin @@ -129,6 +134,10 @@ spec: {{ range .Cmd }} - {{.}} {{ end }} + args: + {{ range .Arg }} + - {{.}} + {{ end }} env: - name: PATH value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin @@ -171,6 +180,7 @@ spec: var ( defaultCtrName = "testCtr" defaultCtrCmd = []string{"top"} + defaultCtrArg = []string{"-d", "1.5"} defaultCtrImage = ALPINE defaultPodName = "testPod" defaultDeploymentName = "testDeployment" @@ -322,6 +332,7 @@ type Ctr struct { Name string Image string Cmd []string + Arg []string SecurityContext bool Caps bool CapAdd []string @@ -332,7 +343,7 @@ type Ctr struct { // getCtr takes a list of ctrOptions and returns a Ctr with sane defaults // and the configured options func getCtr(options ...ctrOption) *Ctr { - c := Ctr{defaultCtrName, defaultCtrImage, defaultCtrCmd, true, false, nil, nil, ""} + c := Ctr{defaultCtrName, defaultCtrImage, defaultCtrCmd, defaultCtrArg, true, false, nil, nil, ""} for _, option := range options { option(&c) } @@ -347,6 +358,12 @@ func withCmd(cmd []string) ctrOption { } } +func withArg(arg []string) ctrOption { + return func(c *Ctr) { + c.Arg = arg + } +} + func withImage(img string) ctrOption { return func(c *Ctr) { c.Image = img @@ -438,14 +455,50 @@ var _ = Describe("Podman generate kube", func() { kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod)}) + inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ .Config.Cmd }}'"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(Equal(0)) + // Use the defined command to override the image's command + correctCmd := "[" + strings.Join(defaultCtrCmd, " ") + " " + strings.Join(defaultCtrArg, " ") + Expect(inspect.OutputToString()).To(ContainSubstring(correctCmd)) + }) + + It("podman play kube test correct command with only set command in yaml file", func() { + pod := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"}), withArg(nil)))) + err := generatePodKubeYaml(pod, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube.ExitCode()).To(Equal(0)) + + inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ .Config.Cmd }}'"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(Equal(0)) + // Use the defined command to override the image's command, and don't set the args + // so the full command in result should not contains the image's command + Expect(inspect.OutputToString()).To(ContainSubstring(`[echo hello]`)) + }) + + It("podman play kube test correct command with only set args in yaml file", func() { + pod := getPod(withCtr(getCtr(withImage(redis), withCmd(nil), withArg([]string{"echo", "hello"})))) + err := generatePodKubeYaml(pod, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube.ExitCode()).To(Equal(0)) + + inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ .Config.Cmd }}'"}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) - Expect(inspect.OutputToString()).To(ContainSubstring(defaultCtrCmd[0])) + // this image's ENTRYPOINT is called `docker-entrypoint.sh` + // so result should be `docker-entrypoint.sh + withArg(...)` + Expect(inspect.OutputToString()).To(ContainSubstring(`[docker-entrypoint.sh echo hello]`)) }) It("podman play kube test correct output", func() { - p := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"})))) + p := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"}), withArg([]string{"world"})))) err := generatePodKubeYaml(p, kubeYaml) Expect(err).To(BeNil()) @@ -457,12 +510,12 @@ var _ = Describe("Podman generate kube", func() { logs := podmanTest.Podman([]string{"logs", getCtrNameInPod(p)}) logs.WaitWithDefaultTimeout() Expect(logs.ExitCode()).To(Equal(0)) - Expect(logs.OutputToString()).To(ContainSubstring("hello")) + Expect(logs.OutputToString()).To(ContainSubstring("hello world")) inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(p), "--format", "'{{ .Config.Cmd }}'"}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) - Expect(inspect.OutputToString()).To(ContainSubstring("hello")) + Expect(inspect.OutputToString()).To(ContainSubstring(`[echo hello world]`)) }) It("podman play kube test hostname", func() { @@ -498,7 +551,7 @@ var _ = Describe("Podman generate kube", func() { It("podman play kube cap add", func() { capAdd := "CAP_SYS_ADMIN" - ctr := getCtr(withCapAdd([]string{capAdd}), withCmd([]string{"cat", "/proc/self/status"})) + ctr := getCtr(withCapAdd([]string{capAdd}), withCmd([]string{"cat", "/proc/self/status"}), withArg(nil)) pod := getPod(withCtr(ctr)) err := generatePodKubeYaml(pod, kubeYaml) @@ -556,7 +609,7 @@ var _ = Describe("Podman generate kube", func() { } ctrAnnotation := "container.seccomp.security.alpha.kubernetes.io/" + defaultCtrName - ctr := getCtr(withCmd([]string{"pwd"})) + ctr := getCtr(withCmd([]string{"pwd"}), withArg(nil)) pod := getPod(withCtr(ctr), withAnnotation(ctrAnnotation, "localhost/"+filepath.Base(jsonFile))) err = generatePodKubeYaml(pod, kubeYaml) @@ -582,7 +635,7 @@ var _ = Describe("Podman generate kube", func() { } defer os.Remove(jsonFile) - ctr := getCtr(withCmd([]string{"pwd"})) + ctr := getCtr(withCmd([]string{"pwd"}), withArg(nil)) pod := getPod(withCtr(ctr), withAnnotation("seccomp.security.alpha.kubernetes.io/pod", "localhost/"+filepath.Base(jsonFile))) err = generatePodKubeYaml(pod, kubeYaml) @@ -734,10 +787,12 @@ spec: Expect(kube.ExitCode()).To(Equal(0)) podNames := getPodNamesInDeployment(deployment) - inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(&podNames[0])}) + inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(&podNames[0]), "--format", "'{{ .Config.Cmd }}'"}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) - Expect(inspect.OutputToString()).To(ContainSubstring(defaultCtrCmd[0])) + // yaml's command shuold override the image's Entrypoint + correctCmd := "[" + strings.Join(defaultCtrCmd, " ") + " " + strings.Join(defaultCtrArg, " ") + Expect(inspect.OutputToString()).To(ContainSubstring(correctCmd)) }) It("podman play kube deployment more than 1 replica test correct command", func() { @@ -752,11 +807,12 @@ spec: Expect(kube.ExitCode()).To(Equal(0)) podNames := getPodNamesInDeployment(deployment) + correctCmd := "[" + strings.Join(defaultCtrCmd, " ") + " " + strings.Join(defaultCtrArg, " ") for i = 0; i < numReplicas; i++ { - inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(&podNames[i])}) + inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(&podNames[i]), "--format", "'{{ .Config.Cmd }}'"}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) - Expect(inspect.OutputToString()).To(ContainSubstring(defaultCtrCmd[0])) + Expect(inspect.OutputToString()).To(ContainSubstring(correctCmd)) } }) }) diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index 57737ad59..016eaaa99 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/containers/libpod/v2/pkg/rootless" . "github.com/containers/libpod/v2/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -238,17 +239,20 @@ var _ = Describe("Podman pod create", func() { }) It("podman create pod with IP address", func() { - SkipIfRootless() name := "test" ip := GetRandomIPAddress() podCreate := podmanTest.Podman([]string{"pod", "create", "--ip", ip, "--name", name}) podCreate.WaitWithDefaultTimeout() - Expect(podCreate.ExitCode()).To(Equal(0)) - - podResolvConf := podmanTest.Podman([]string{"run", "--pod", name, "-ti", "--rm", ALPINE, "ip", "addr"}) - podResolvConf.WaitWithDefaultTimeout() - Expect(podResolvConf.ExitCode()).To(Equal(0)) - Expect(strings.Contains(podResolvConf.OutputToString(), ip)).To(BeTrue()) + // Rootless should error + if rootless.IsRootless() { + Expect(podCreate.ExitCode()).To(Equal(125)) + } else { + Expect(podCreate.ExitCode()).To(Equal(0)) + podResolvConf := podmanTest.Podman([]string{"run", "--pod", name, "-ti", "--rm", ALPINE, "ip", "addr"}) + podResolvConf.WaitWithDefaultTimeout() + Expect(podResolvConf.ExitCode()).To(Equal(0)) + Expect(strings.Contains(podResolvConf.OutputToString(), ip)).To(BeTrue()) + } }) It("podman create pod with IP address and no infra should fail", func() { @@ -262,17 +266,20 @@ var _ = Describe("Podman pod create", func() { It("podman create pod with MAC address", func() { SkipIfRemote() - SkipIfRootless() name := "test" mac := "92:d0:c6:0a:29:35" podCreate := podmanTest.Podman([]string{"pod", "create", "--mac-address", mac, "--name", name}) podCreate.WaitWithDefaultTimeout() - Expect(podCreate.ExitCode()).To(Equal(0)) - - podResolvConf := podmanTest.Podman([]string{"run", "--pod", name, "-ti", "--rm", ALPINE, "ip", "addr"}) - podResolvConf.WaitWithDefaultTimeout() - Expect(podResolvConf.ExitCode()).To(Equal(0)) - Expect(strings.Contains(podResolvConf.OutputToString(), mac)).To(BeTrue()) + // Rootless should error + if rootless.IsRootless() { + Expect(podCreate.ExitCode()).To(Equal(125)) + } else { + Expect(podCreate.ExitCode()).To(Equal(0)) + podResolvConf := podmanTest.Podman([]string{"run", "--pod", name, "-ti", "--rm", ALPINE, "ip", "addr"}) + podResolvConf.WaitWithDefaultTimeout() + Expect(podResolvConf.ExitCode()).To(Equal(0)) + Expect(strings.Contains(podResolvConf.OutputToString(), mac)).To(BeTrue()) + } }) It("podman create pod with MAC address and no infra should fail", func() { diff --git a/test/e2e/pod_inspect_test.go b/test/e2e/pod_inspect_test.go index 5e3634435..16bf1c4c9 100644 --- a/test/e2e/pod_inspect_test.go +++ b/test/e2e/pod_inspect_test.go @@ -1,8 +1,11 @@ package integration import ( + "encoding/json" "os" + "github.com/containers/libpod/v2/libpod/define" + . "github.com/containers/libpod/v2/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -79,4 +82,22 @@ var _ = Describe("Podman pod inspect", func() { index := len(inspectCreateCommand) - len(createCommand) Expect(inspectCreateCommand[index:]).To(Equal(createCommand)) }) + + It("podman pod inspect outputs port bindings", func() { + podName := "testPod" + create := podmanTest.Podman([]string{"pod", "create", "--name", podName, "-p", "8080:80"}) + create.WaitWithDefaultTimeout() + Expect(create.ExitCode()).To(Equal(0)) + + inspectOut := podmanTest.Podman([]string{"pod", "inspect", podName}) + inspectOut.WaitWithDefaultTimeout() + Expect(inspectOut.ExitCode()).To(Equal(0)) + + inspectJSON := new(define.InspectPodData) + err := json.Unmarshal(inspectOut.Out.Contents(), inspectJSON) + Expect(err).To(BeNil()) + Expect(inspectJSON.InfraConfig).To(Not(BeNil())) + Expect(len(inspectJSON.InfraConfig.PortBindings["80/tcp"])).To(Equal(1)) + Expect(inspectJSON.InfraConfig.PortBindings["80/tcp"][0].HostPort).To(Equal("8080")) + }) }) diff --git a/test/e2e/run_apparmor_test.go b/test/e2e/run_apparmor_test.go new file mode 100644 index 000000000..344309b93 --- /dev/null +++ b/test/e2e/run_apparmor_test.go @@ -0,0 +1,158 @@ +// +build !remote + +package integration + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/containers/common/pkg/apparmor" + . "github.com/containers/libpod/v2/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func skipIfAppArmorEnabled() { + if apparmor.IsEnabled() { + Skip("Apparmor is enabled") + } +} +func skipIfAppArmorDisabled() { + if !apparmor.IsEnabled() { + Skip("Apparmor is not enabled") + } +} + +var _ = Describe("Podman run", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + podmanTest.SeedImages() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + processTestResult(f) + + }) + + It("podman run apparmor default", func() { + skipIfAppArmorDisabled() + session := podmanTest.Podman([]string{"create", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + cid := session.OutputToString() + // Verify that apparmor.Profile is being set + inspect := podmanTest.InspectContainer(cid) + Expect(inspect[0].AppArmorProfile).To(Equal(apparmor.Profile)) + }) + + It("podman run no apparmor --privileged", func() { + skipIfAppArmorDisabled() + session := podmanTest.Podman([]string{"create", "--privileged", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + cid := session.OutputToString() + // Verify that apparmor.Profile is being set + inspect := podmanTest.InspectContainer(cid) + Expect(inspect[0].AppArmorProfile).To(Equal("")) + }) + + It("podman run no apparmor --security-opt=apparmor.Profile --privileged", func() { + skipIfAppArmorDisabled() + session := podmanTest.Podman([]string{"create", "--security-opt", fmt.Sprintf("apparmor=%s", apparmor.Profile), "--privileged", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + cid := session.OutputToString() + // Verify that apparmor.Profile is being set + inspect := podmanTest.InspectContainer(cid) + Expect(inspect[0].AppArmorProfile).To(Equal(apparmor.Profile)) + }) + + It("podman run apparmor aa-test-profile", func() { + skipIfAppArmorDisabled() + aaProfile := ` +#include <tunables/global> +profile aa-test-profile flags=(attach_disconnected,mediate_deleted) { + #include <abstractions/base> + deny mount, + deny /sys/[^f]*/** wklx, + deny /sys/f[^s]*/** wklx, + deny /sys/fs/[^c]*/** wklx, + deny /sys/fs/c[^g]*/** wklx, + deny /sys/fs/cg[^r]*/** wklx, + deny /sys/firmware/efi/efivars/** rwklx, + deny /sys/kernel/security/** rwklx, +} +` + aaFile := filepath.Join(os.TempDir(), "aaFile") + Expect(ioutil.WriteFile(aaFile, []byte(aaProfile), 0755)).To(BeNil()) + parse := SystemExec("apparmor_parser", []string{"-Kr", aaFile}) + Expect(parse.ExitCode()).To(Equal(0)) + + session := podmanTest.Podman([]string{"create", "--security-opt", "apparmor=aa-test-profile", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + cid := session.OutputToString() + // Verify that apparmor.Profile is being set + inspect := podmanTest.InspectContainer(cid) + Expect(inspect[0].AppArmorProfile).To(Equal("aa-test-profile")) + }) + + It("podman run apparmor invalid", func() { + skipIfAppArmorDisabled() + session := podmanTest.Podman([]string{"run", "--security-opt", "apparmor=invalid", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).ToNot(Equal(0)) + }) + + It("podman run apparmor unconfined", func() { + skipIfAppArmorDisabled() + session := podmanTest.Podman([]string{"create", "--security-opt", "apparmor=unconfined", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + cid := session.OutputToString() + // Verify that apparmor.Profile is being set + inspect := podmanTest.InspectContainer(cid) + Expect(inspect[0].AppArmorProfile).To(Equal("unconfined")) + }) + + It("podman run apparmor disabled --security-opt apparmor fails", func() { + skipIfAppArmorEnabled() + // Should fail if user specifies apparmor on disabled system + session := podmanTest.Podman([]string{"create", "--security-opt", fmt.Sprintf("apparmor=%s", apparmor.Profile), ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).ToNot(Equal(0)) + }) + + It("podman run apparmor disabled no default", func() { + skipIfAppArmorEnabled() + // Should succeed if user specifies apparmor on disabled system + session := podmanTest.Podman([]string{"create", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + cid := session.OutputToString() + // Verify that apparmor.Profile is being set + inspect := podmanTest.InspectContainer(cid) + Expect(inspect[0].AppArmorProfile).To(Equal("")) + }) +}) diff --git a/test/e2e/run_entrypoint_test.go b/test/e2e/run_entrypoint_test.go index c947fa863..e6604a21e 100644 --- a/test/e2e/run_entrypoint_test.go +++ b/test/e2e/run_entrypoint_test.go @@ -101,6 +101,11 @@ ENTRYPOINT ["grep", "Alpine", "/etc/os-release"] session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) Expect(session.LineInOuputStartsWith("Linux")).To(BeTrue()) + + session = podmanTest.Podman([]string{"run", "--entrypoint", "", "foobar.com/entrypoint:latest", "uname"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.LineInOuputStartsWith("Linux")).To(BeTrue()) }) It("podman run user entrypoint with command overrides image entrypoint and image cmd", func() { diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 6c049c5c1..9357145ab 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -88,6 +88,20 @@ var _ = Describe("Podman run networking", func() { Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal("")) }) + It("podman run -p 8080:80/TCP", func() { + name := "testctr" + // "TCP" in upper characters + session := podmanTest.Podman([]string{"create", "-t", "-p", "8080:80/TCP", "--name", name, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + inspectOut := podmanTest.InspectContainer(name) + Expect(len(inspectOut)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1)) + // "tcp" in lower characters + Expect(len(inspectOut[0].NetworkSettings.Ports["80/tcp"])).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostPort).To(Equal("8080")) + Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal("")) + }) + It("podman run -p 80/udp", func() { name := "testctr" session := podmanTest.Podman([]string{"create", "-t", "-p", "80/udp", "--name", name, ALPINE, "/bin/sh"}) diff --git a/test/e2e/system_df_test.go b/test/e2e/system_df_test.go index e882756f9..3d7aaf659 100644 --- a/test/e2e/system_df_test.go +++ b/test/e2e/system_df_test.go @@ -60,4 +60,18 @@ var _ = Describe("podman system df", func() { Expect(containers[1]).To(Equal("2")) Expect(volumes[2]).To(Equal("1")) }) + + It("podman system df image with no tag", func() { + session := podmanTest.PodmanNoCache([]string{"create", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.PodmanNoCache([]string{"image", "untag", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.PodmanNoCache([]string{"system", "df"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) }) diff --git a/test/e2e/systemd_test.go b/test/e2e/systemd_test.go index 143b8f59f..05c0d3fd0 100644 --- a/test/e2e/systemd_test.go +++ b/test/e2e/systemd_test.go @@ -112,5 +112,40 @@ WantedBy=multi-user.target systemctl.WaitWithDefaultTimeout() Expect(systemctl.ExitCode()).To(Equal(0)) Expect(strings.Contains(systemctl.OutputToString(), "State:")).To(BeTrue()) + + result := podmanTest.Podman([]string{"inspect", ctrName}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + conData := result.InspectContainerToJSON() + Expect(len(conData)).To(Equal(1)) + Expect(conData[0].Config.SystemdMode).To(BeTrue()) + }) + + It("podman create container with systemd entrypoint triggers systemd mode", func() { + ctrName := "testCtr" + run := podmanTest.Podman([]string{"create", "--name", ctrName, "--entrypoint", "/sbin/init", ubi_init}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"inspect", ctrName}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + conData := result.InspectContainerToJSON() + Expect(len(conData)).To(Equal(1)) + Expect(conData[0].Config.SystemdMode).To(BeTrue()) + }) + + It("podman create container with systemd=always triggers systemd mode", func() { + ctrName := "testCtr" + run := podmanTest.Podman([]string{"create", "--name", ctrName, "--systemd", "always", ALPINE}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"inspect", ctrName}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + conData := result.InspectContainerToJSON() + Expect(len(conData)).To(Equal(1)) + Expect(conData[0].Config.SystemdMode).To(BeTrue()) }) }) diff --git a/test/e2e/version_test.go b/test/e2e/version_test.go index 775be6511..eb1d1b733 100644 --- a/test/e2e/version_test.go +++ b/test/e2e/version_test.go @@ -55,17 +55,29 @@ var _ = Describe("Podman version", func() { }) It("podman version --format json", func() { - session := podmanTest.Podman([]string{"version", "--format", "json"}) - session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) - Expect(session.IsJSONOutputValid()).To(BeTrue()) - }) - - It("podman version --format json", func() { - session := podmanTest.Podman([]string{"version", "--format", "{{ json .}}"}) - session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) - Expect(session.IsJSONOutputValid()).To(BeTrue()) + tests := []struct { + input string + success bool + exitCode int + }{ + {"json", true, 0}, + {" json", true, 0}, + {"json ", true, 0}, + {" json ", true, 0}, + {"{{json .}}", true, 0}, + {"{{ json .}}", true, 0}, + {"{{json . }}", true, 0}, + {" {{ json . }} ", true, 0}, + {"{{json }}", false, 125}, + {"{{json .", false, 125}, + {"json . }}", false, 0}, // Note: this does NOT fail but produces garbage + } + for _, tt := range tests { + session := podmanTest.Podman([]string{"version", "--format", tt.input}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(tt.exitCode)) + Expect(session.IsJSONOutputValid()).To(Equal(tt.success)) + } }) It("podman version --format GO template", func() { diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats index 9a6b39057..478ff06bb 100644 --- a/test/system/200-pod.bats +++ b/test/system/200-pod.bats @@ -153,9 +153,13 @@ function random_ip() { # Create a pod with all the desired options # FIXME: --ip=$ip fails: # Error adding network: failed to allocate all requested IPs + local mac_option="--mac-address=$mac" + if is_rootless; then + mac_option= + fi run_podman pod create --name=mypod \ --pod-id-file=$pod_id_file \ - --mac-address=$mac \ + $mac_option \ --hostname=$hostname \ --add-host "$add_host_n:$add_host_ip" \ --dns "$dns_server" \ @@ -168,7 +172,7 @@ function random_ip() { is "$(<$pod_id_file)" "$pod_id" "contents of pod-id-file" # Check each of the options - if ! is_rootless; then + if [ -n "$mac_option" ]; then run_podman run --rm --pod mypod $IMAGE ip link show # 'ip' outputs hex in lower-case, ${expr,,} converts UC to lc is "$output" ".* link/ether ${mac,,} " "requested MAC address was set" |