diff options
39 files changed, 527 insertions, 131 deletions
@@ -1,9 +1,9 @@ ![PODMAN logo](logo/podman-logo-source.svg) -# Library and tool for running OCI-based containers in Pods +# Podman: A tool for managing OCI containers and pods -Libpod provides a library for applications looking to use the Container Pod concept, -popularized by Kubernetes. Libpod also contains the Pod Manager tool `(Podman)`. Podman manages pods, containers, container images, and container volumes. +Podman (the POD MANager) is a tool for managing containers and images, volumes mounted into those containers, and pods made from groups of containers. +Podman is based on libpod, a library for container lifecycle management that is also contained in this repository. The libpod library provides APIs for managing containers, pods, container images, and volumes. * [Latest Version: 2.0.2](https://github.com/containers/libpod/releases/latest) * Latest Remote client for Windows @@ -15,26 +15,24 @@ popularized by Kubernetes. Libpod also contains the Pod Manager tool `(Podman)` ## Overview and scope -At a high level, the scope of libpod and Podman is the following: +At a high level, the scope of Podman and libpod is the following: -* Support multiple image formats including the OCI and Docker image formats. -* Support for multiple means to download images including trust & image verification. -* Container image management (managing image layers, overlay filesystems, etc). -* Full management of container lifecycle. -* Support for pods to manage groups of containers together. +* Support for multiple container image formats, including OCI and Docker images. +* Full management of those images, including pulling from various sources (including trust and verification), creating (built via Containerfile or Dockerfile or committed from a container), and pushing to registries and other storage backends. +* Full management of container lifecycle, including creation (both from an image and from an exploded root filesystem), running, checkpointing and restoring (via CRIU), and removal. +* Support for pods, groups of containers that share resources and are managed together. * Resource isolation of containers and pods. -* Support for a Docker-compatible CLI interface through Podman. +* Support for a Docker-compatible CLI interface. * Support for a REST API providing both a Docker-compatible interface and an improved interface exposing advanced Podman functionality. -* Integration with CRI-O to share containers and backend code. +* In the future, integration with [CRI-O](https://github.com/cri-o/cri-o) to share containers and backend code. Podman presently only supports running containers on Linux. However, we are building a remote client which can run on Windows and OS X and manage Podman containers on a Linux system via the REST API using SSH tunneling. ## Roadmap -1. Complete the Podman REST API and Podman v2, which will be able to connect to remote Podman instances via this API -1. Integrate libpod into CRI-O to replace its existing container management backend -1. Further work on the podman pod command -1. Further improvements on rootless containers +1. Further improvements to the REST API, with a focus on bugfixes and implementing missing functionality +1. Integrate libpod into [CRI-O](https://github.com/cri-o/cri-o) to replace its existing container management backend +1. Improvements on rootless containers, with a focus on improving the user experience and exposing presently-unavailable features when possible ## Communications @@ -67,10 +65,10 @@ A little configuration by an administrator is required before rootless Podman ca ## Out of scope -* Specializing in signing and pushing images to various storage backends. +* Specialized signing and pushing of images to various storage backends. See [Skopeo](https://github.com/containers/skopeo/) for those tasks. -* Container runtimes daemons for working with the Kubernetes CRI interface. - [CRI-O](https://github.com/cri-o/cri-o) specializes in that. +* Support for the Kubernetes CRI interface for container management. + The [CRI-O](https://github.com/cri-o/cri-o) daemon specializes in that. * Supporting `docker-compose`. We believe that Kubernetes is the defacto standard for composing Pods and for orchestrating containers, making Kubernetes YAML a defacto standard file format. Hence, Podman allows the diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index a26bbf718..46f78cdba 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -155,6 +155,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", ) @@ -459,6 +463,11 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "tz", containerConfig.TZ(), "Set timezone in container", ) + createFlags.StringVar( + &cf.Umask, + "umask", containerConfig.Umask(), + "Set umask in container", + ) createFlags.StringSliceVar( &cf.UIDMap, "uidmap", []string{}, diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index a544846aa..2bea8b0b4 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -93,6 +93,7 @@ type ContainerCLIOpts struct { TmpFS []string TTY bool Timezone string + Umask string UIDMap []string Ulimit []string User string diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 416c6f6ec..731085731 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -613,6 +613,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.Remove = c.Rm s.StopTimeout = &c.StopTimeout s.Timezone = c.Timezone + s.Umask = c.Umask return nil } 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 4eeed13d4..480b5e0f0 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -79,6 +79,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/completions/bash/podman b/completions/bash/podman index 458090ac4..eb727ef63 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2119,12 +2119,13 @@ _podman_container_run() { --shm-size --stop-signal --stop-timeout - --tmpfs - --tz --subgidname --subuidname --sysctl --systemd + --tmpfs + --tz + --umask --uidmap --ulimit --user -u diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index a422dd184..b4456225e 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -234,6 +234,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>'. @@ -833,6 +839,10 @@ standard input. Set timezone in container. This flag takes area-based timezones, GMT time, as well as `local`, which sets the timezone in the container to match the host machine. See `/usr/share/zoneinfo/` for valid timezones. +**--umask**=*umask* + +Set the umask inside the container. Defaults to `0022`. + **--uidmap**=*container_uid:host_uid:amount* UID map for the user namespace. Using this flag will run the container with user namespace enabled. It conflicts with the `--userns` and `--subuidname` flags. @@ -1120,14 +1130,13 @@ required for VPN, without it containers need to be run with the --network=host f Environment variables within containers can be set using multiple different options: This section describes the precedence. -Precedence Order: - **--env-host** : Host environment of the process executing Podman is added. - - Container image : Any environment variables specified in the container image. - - **--env-file** : Any environment variables specified via env-files. If multiple files specified, then they override each other in order of entry. +Precedence order (later entries override earlier entries): - **--env** : Any environment variables specified will override previous settings. +- **--env-host** : Host environment of the process executing Podman is added. +- **--http-proxy**: By default, several environment variables will be passed in from the host, such as **http_proxy** and **no_proxy**. See **--http-proxy** for details. +- Container image : Any environment variables specified in the container image. +- **--env-file** : Any environment variables specified via env-files. If multiple files specified, then they override each other in order of entry. +- **--env** : Any environment variables specified will override previous settings. Create containers and set the environment ending with a __*__ and a ***** 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 a7fd5a7eb..303d025b0 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -247,6 +247,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_. @@ -874,6 +880,10 @@ standard input. Set timezone in container. This flag takes area-based timezones, GMT time, as well as `local`, which sets the timezone in the container to match the host machine. See `/usr/share/zoneinfo/` for valid timezones. +**--umask**=*umask* + +Set the umask inside the container. Defaults to `0022`. + **--uidmap**=*container_uid*:*host_uid*:*amount* Run the container in a new user namespace using the supplied mapping. This option conflicts @@ -1399,9 +1409,10 @@ required for VPN, without it containers need to be run with the **--network=host ## ENVIRONMENT Environment variables within containers can be set using multiple different options, -in the following order of precedence: +in the following order of precedence (later entries override earlier entries): - **--env-host**: Host environment of the process executing Podman is added. +- **--http-proxy**: By default, several environment variables will be passed in from the host, such as **http_proxy** and **no_proxy**. See **--http-proxy** for details. - Container image: Any environment variables specified in the container image. - **--env-file**: Any environment variables specified via env-files. If multiple files specified, then they override each other in order of entry. - **--env**: Any environment variables specified will override previous settings. @@ -41,7 +41,7 @@ require ( github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 github.com/opencontainers/runc v1.0.0-rc91.0.20200708210054-ce54a9d4d79b github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2 - github.com/opencontainers/runtime-tools v0.9.0 + github.com/opencontainers/runtime-tools v0.9.1-0.20200714183735-07406c5828aa github.com/opencontainers/selinux v1.6.0 github.com/opentracing/opentracing-go v1.2.0 github.com/pkg/errors v0.9.1 @@ -342,6 +342,8 @@ github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2/go.m github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/runtime-tools v0.9.0 h1:FYgwVsKRI/H9hU32MJ/4MLOzXWodKK5zsQavY8NPMkU= github.com/opencontainers/runtime-tools v0.9.0/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/runtime-tools v0.9.1-0.20200714183735-07406c5828aa h1:iyj+fFHVBn0xOalz9UChYzSU1K0HJ+d75b4YqShBRhI= +github.com/opencontainers/runtime-tools v0.9.1-0.20200714183735-07406c5828aa/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/selinux v1.3.0/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs= github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= github.com/opencontainers/selinux v1.5.2/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= diff --git a/libpod/container.go b/libpod/container.go index fda018640..8a69df685 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -437,6 +437,9 @@ type ContainerConfig struct { // Timezone is the timezone inside the container. // Local means it has the same timezone as the host machine Timezone string `json:"timezone,omitempty"` + + // Umask is the umask inside the container. + Umask string `json:"umask,omitempty"` } // ContainerNamedVolume is a named volume that will be mounted into the @@ -1276,5 +1279,8 @@ func (c *Container) AutoRemove() bool { func (c *Container) Timezone() string { return c.config.Timezone +} +func (c *Container) Umask() string { + return c.config.Umask } diff --git a/libpod/container_exec.go b/libpod/container_exec.go index bd04ee9b9..a16aea06d 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -729,10 +729,6 @@ func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resi return -1, err } - if exitCode != 0 { - return exitCode, errors.Wrapf(define.ErrOCIRuntime, "exec session exited with non-zero exit code %d", exitCode) - } - return exitCode, nil } diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 680776dba..a0d223c8c 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -325,6 +325,14 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp ctrConfig.Timezone = c.config.Timezone + // Pad Umask to 4 characters + if len(c.config.Umask) < 4 { + pad := strings.Repeat("0", 4-len(c.config.Umask)) + ctrConfig.Umask = pad + c.config.Umask + } else { + ctrConfig.Umask = c.config.Umask + } + return ctrConfig } diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 1c21f2ff9..edea62a0d 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -355,6 +355,14 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { g.SetProcessGID(uint32(execUser.Gid)) } + if c.config.Umask != "" { + decVal, err := strconv.ParseUint(c.config.Umask, 8, 32) + if err != nil { + return nil, errors.Wrapf(err, "Invalid Umask Value") + } + g.SetProcessUmask(uint32(decVal)) + } + // Add addition groups if c.config.GroupAdd is not empty if len(c.config.Groups) > 0 { gids, err := lookup.GetContainerGroups(c.config.Groups, c.state.Mountpoint, overrides) diff --git a/libpod/define/config.go b/libpod/define/config.go index 64b24d9e2..6c426f2ec 100644 --- a/libpod/define/config.go +++ b/libpod/define/config.go @@ -20,6 +20,8 @@ var ( NameRegex = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_.-]*$") // RegexError is thrown in presence of an invalid container/pod name. RegexError = errors.Wrapf(ErrInvalidArg, "names must match [a-zA-Z0-9][a-zA-Z0-9_.-]*") + // UmaskRegex is a regular expression to validate Umask. + UmaskRegex = regexp.MustCompile(`^[0-7]{1,4}$`) ) const ( diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go index fbd9da3e7..a08cb3de6 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -61,6 +61,8 @@ type InspectContainerConfig struct { // systemd mode, the container configuration is customized to optimize // running systemd in the container. SystemdMode bool `json:"SystemdMode,omitempty"` + // Umask is the umask inside the container. + Umask string `json:"Umask,omitempty"` } // InspectRestartPolicy holds information about the container's restart policy. diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go index d341ca7b5..7c2a3e0f2 100644 --- a/libpod/events/journal_linux.go +++ b/libpod/events/journal_linux.go @@ -90,6 +90,13 @@ func (e EventJournalD) Read(ctx context.Context, options ReadOptions) error { return err } for { + select { + case <-ctx.Done(): + // the consumer has cancelled + return nil + default: + // fallthrough + } if _, err := j.Next(); err != nil { return err } diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go index 28d0dc07e..b70102450 100644 --- a/libpod/events/logfile.go +++ b/libpod/events/logfile.go @@ -63,6 +63,14 @@ func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error { } }() for line := range t.Lines { + select { + case <-ctx.Done(): + // the consumer has cancelled + return nil + default: + // fallthrough + } + event, err := newEventFromJSONString(line.Text) if err != nil { return err diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go index b04742974..4818f8dc4 100644 --- a/libpod/healthcheck.go +++ b/libpod/healthcheck.go @@ -92,7 +92,7 @@ func (c *Container) runHealthCheck() (define.HealthCheckStatus, error) { hcResult := define.HealthCheckSuccess config := new(ExecConfig) config.Command = newCommand - _, hcErr := c.Exec(config, streams, nil) + exitCode, hcErr := c.Exec(config, streams, nil) if hcErr != nil { errCause := errors.Cause(hcErr) hcResult = define.HealthCheckFailure @@ -104,6 +104,9 @@ func (c *Container) runHealthCheck() (define.HealthCheckStatus, error) { } else { returnCode = 125 } + } else if exitCode != 0 { + hcResult = define.HealthCheckFailure + returnCode = 1 } timeEnd := time.Now() if c.HealthCheckConfig().StartPeriod > 0 { diff --git a/libpod/options.go b/libpod/options.go index 40cf452db..41b0d7212 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1607,6 +1607,20 @@ func WithTimezone(path string) CtrCreateOption { } } +// WithUmask sets the umask in the container +func WithUmask(umask string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + if !define.UmaskRegex.MatchString(umask) { + return errors.Wrapf(define.ErrInvalidArg, "Invalid umask string %s", umask) + } + ctr.config.Umask = umask + return nil + } +} + // Pod Creation Options // WithPodName sets the name of the pod. diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go index 5acc94153..9d5cb5045 100644 --- a/pkg/api/handlers/compat/events.go +++ b/pkg/api/handlers/compat/events.go @@ -1,9 +1,10 @@ package compat import ( - "context" + "encoding/json" "fmt" "net/http" + "sync" "github.com/containers/libpod/v2/libpod" "github.com/containers/libpod/v2/libpod/events" @@ -15,77 +16,132 @@ import ( "github.com/sirupsen/logrus" ) +// filtersFromRequests extracts the "filters" parameter from the specified +// http.Request. The paramater can either be a `map[string][]string` as done +// in new versions of Docker and libpod, or a `map[string]map[string]bool` as +// done in older versions of Docker. We have to do a bit of Yoga to support +// both - just as Docker does as well. +// +// Please refer to https://github.com/containers/podman/issues/6899 for some +// background. +func filtersFromRequest(r *http.Request) ([]string, error) { + var ( + compatFilters map[string]map[string]bool + filters map[string][]string + libpodFilters []string + ) + raw := []byte(r.Form.Get("filters")) + + // Backwards compat with older versions of Docker. + if err := json.Unmarshal(raw, &compatFilters); err == nil { + for filterKey, filterMap := range compatFilters { + for filterValue, toAdd := range filterMap { + if toAdd { + libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", filterKey, filterValue)) + } + } + } + return libpodFilters, nil + } + + if err := json.Unmarshal(raw, &filters); err != nil { + return nil, err + } + + for filterKey, filterSlice := range filters { + for _, filterValue := range filterSlice { + libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", filterKey, filterValue)) + } + } + + return libpodFilters, nil +} + +// NOTE: this endpoint serves both the docker-compatible one and the new libpod +// one. func GetEvents(w http.ResponseWriter, r *http.Request) { var ( - fromStart bool - eventsError error - decoder = r.Context().Value("decoder").(*schema.Decoder) - runtime = r.Context().Value("runtime").(*libpod.Runtime) + fromStart bool + decoder = r.Context().Value("decoder").(*schema.Decoder) + runtime = r.Context().Value("runtime").(*libpod.Runtime) + json = jsoniter.ConfigCompatibleWithStandardLibrary // FIXME: this should happen on the package level ) + // NOTE: the "filters" parameter is extracted separately for backwards + // compat via `fitlerFromRequest()`. query := struct { - Since string `schema:"since"` - Until string `schema:"until"` - Filters map[string][]string `schema:"filters"` - Stream bool `schema:"stream"` + Since string `schema:"since"` + Until string `schema:"until"` + Stream bool `schema:"stream"` }{ Stream: true, } if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) - } - - var libpodFilters = []string{} - if _, found := r.URL.Query()["filters"]; found { - for k, v := range query.Filters { - libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) - } + return } if len(query.Since) > 0 || len(query.Until) > 0 { fromStart = true } - eventCtx, eventCancel := context.WithCancel(r.Context()) - eventChannel := make(chan *events.Event) - go func() { - readOpts := events.ReadOptions{FromStart: fromStart, Stream: query.Stream, Filters: libpodFilters, EventChannel: eventChannel, Since: query.Since, Until: query.Until} - eventsError = runtime.Events(eventCtx, readOpts) - }() - if eventsError != nil { - utils.InternalServerError(w, eventsError) - eventCancel() - close(eventChannel) + libpodFilters, err := filtersFromRequest(r) + if err != nil { + utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) return } - // If client disappears we need to stop listening for events - go func(done <-chan struct{}) { - <-done - eventCancel() - if _, ok := <-eventChannel; ok { - close(eventChannel) + eventChannel := make(chan *events.Event) + errorChannel := make(chan error) + + // Start reading events. + go func() { + readOpts := events.ReadOptions{ + FromStart: fromStart, + Stream: query.Stream, + Filters: libpodFilters, + EventChannel: eventChannel, + Since: query.Since, + Until: query.Until, } - }(r.Context().Done()) + errorChannel <- runtime.Events(r.Context(), readOpts) + }() - // Headers need to be written out before turning Writer() over to json encoder - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() - } + var coder *jsoniter.Encoder + var writeHeader sync.Once - json := jsoniter.ConfigCompatibleWithStandardLibrary - coder := json.NewEncoder(w) - coder.SetEscapeHTML(true) + for stream := true; stream; stream = query.Stream { + select { + case err := <-errorChannel: + if err != nil { + utils.InternalServerError(w, err) + return + } + case evt := <-eventChannel: + writeHeader.Do(func() { + // Use a sync.Once so that we write the header + // only once. + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } + coder = json.NewEncoder(w) + coder.SetEscapeHTML(true) + }) - for event := range eventChannel { - e := entities.ConvertToEntitiesEvent(*event) - if err := coder.Encode(e); err != nil { - logrus.Errorf("unable to write json: %q", err) - } - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() + if evt == nil { + continue + } + + e := entities.ConvertToEntitiesEvent(*evt) + if err := coder.Encode(e); err != nil { + logrus.Errorf("unable to write json: %q", err) + } + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } } + } } 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/bindings/test/system_test.go b/pkg/bindings/test/system_test.go index 93141400b..430184f4a 100644 --- a/pkg/bindings/test/system_test.go +++ b/pkg/bindings/test/system_test.go @@ -1,6 +1,7 @@ package test_bindings import ( + "sync" "time" "github.com/containers/libpod/v2/pkg/bindings" @@ -38,22 +39,28 @@ var _ = Describe("Podman system", func() { }) It("podman events", func() { - eChan := make(chan entities.Event, 1) - var messages []entities.Event - cancelChan := make(chan bool, 1) + var name = "top" + _, err := bt.RunTopContainer(&name, bindings.PFalse, nil) + Expect(err).To(BeNil()) + + filters := make(map[string][]string) + filters["container"] = []string{name} + + binChan := make(chan entities.Event) + done := sync.Mutex{} + done.Lock() + eventCounter := 0 go func() { - for e := range eChan { - messages = append(messages, e) + defer done.Unlock() + for range binChan { + eventCounter++ } }() - go func() { - system.Events(bt.conn, eChan, cancelChan, nil, nil, nil, bindings.PFalse) - }() - _, err := bt.RunTopContainer(nil, nil, nil) + err = system.Events(bt.conn, binChan, nil, nil, nil, filters, bindings.PFalse) Expect(err).To(BeNil()) - cancelChan <- true - Expect(len(messages)).To(BeNumerically("==", 5)) + done.Lock() + Expect(eventCounter).To(BeNumerically(">", 0)) }) It("podman system prune - pod,container stopped", func() { 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/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 6dbc45c16..934d5fbac 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -145,6 +145,9 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. if s.Timezone != "" { options = append(options, libpod.WithTimezone(s.Timezone)) } + if s.Umask != "" { + options = append(options, libpod.WithUmask(s.Umask)) + } useSystemd := false switch s.Systemd { diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index c6079be33..84a6c36a0 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -287,6 +287,8 @@ type ContainerSecurityConfig struct { // ReadOnlyFilesystem indicates that everything will be mounted // as read-only ReadOnlyFilesystem bool `json:"read_only_filesystem,omittempty"` + // Umask is the umask the init process of the container will be run with. + Umask string `json:"umask,omitempty"` } // ContainerCgroupConfig contains configuration information about a container's diff --git a/test/e2e/config/containers.conf b/test/e2e/config/containers.conf index 0a07676c4..5f852468d 100644 --- a/test/e2e/config/containers.conf +++ b/test/e2e/config/containers.conf @@ -50,3 +50,5 @@ dns_servers=[ "1.2.3.4", ] dns_options=[ "debug", ] tz = "Pacific/Honolulu" + +umask = "0002" diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go index 23d8dd197..aebbca855 100644 --- a/test/e2e/containers_conf_test.go +++ b/test/e2e/containers_conf_test.go @@ -218,6 +218,17 @@ var _ = Describe("Podman run", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring("HST")) + }) + It("podman run containers.conf umask", func() { + //containers.conf umask set to 0002 + if !strings.Contains(podmanTest.OCIRuntime, "crun") { + Skip("Test only works on crun") + } + session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "sh", "-c", "umask"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("0002")) }) + }) diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index f21f17d39..09b4f5911 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" . "github.com/containers/libpod/v2/test/utils" . "github.com/onsi/ginkgo" @@ -499,4 +500,46 @@ var _ = Describe("Podman create", func() { Expect(data[0].Config.Timezone).To(Equal("local")) }) + It("podman create --umask", func() { + if !strings.Contains(podmanTest.OCIRuntime, "crun") { + Skip("Test only works on crun") + } + + session := podmanTest.Podman([]string{"create", "--name", "default", ALPINE}) + session.WaitWithDefaultTimeout() + inspect := podmanTest.Podman([]string{"inspect", "default"}) + inspect.WaitWithDefaultTimeout() + data := inspect.InspectContainerToJSON() + Expect(len(data)).To(Equal(1)) + Expect(data[0].Config.Umask).To(Equal("0022")) + + session = podmanTest.Podman([]string{"create", "--umask", "0002", "--name", "umask", ALPINE}) + session.WaitWithDefaultTimeout() + inspect = podmanTest.Podman([]string{"inspect", "umask"}) + inspect.WaitWithDefaultTimeout() + data = inspect.InspectContainerToJSON() + Expect(len(data)).To(Equal(1)) + Expect(data[0].Config.Umask).To(Equal("0002")) + + session = podmanTest.Podman([]string{"create", "--umask", "0077", "--name", "fedora", fedoraMinimal}) + session.WaitWithDefaultTimeout() + inspect = podmanTest.Podman([]string{"inspect", "fedora"}) + inspect.WaitWithDefaultTimeout() + data = inspect.InspectContainerToJSON() + Expect(len(data)).To(Equal(1)) + Expect(data[0].Config.Umask).To(Equal("0077")) + + session = podmanTest.Podman([]string{"create", "--umask", "22", "--name", "umask-short", ALPINE}) + session.WaitWithDefaultTimeout() + inspect = podmanTest.Podman([]string{"inspect", "umask-short"}) + inspect.WaitWithDefaultTimeout() + data = inspect.InspectContainerToJSON() + Expect(len(data)).To(Equal(1)) + Expect(data[0].Config.Umask).To(Equal("0022")) + + session = podmanTest.Podman([]string{"create", "--umask", "9999", "--name", "bad", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session.ErrorToString()).To(ContainSubstring("Invalid umask")) + }) }) diff --git a/test/e2e/events_test.go b/test/e2e/events_test.go index 93c51098f..9b527b88e 100644 --- a/test/e2e/events_test.go +++ b/test/e2e/events_test.go @@ -136,6 +136,7 @@ var _ = Describe("Podman events", func() { Expect(ec).To(Equal(0)) test := podmanTest.Podman([]string{"events", "--stream=false", "--format", "json"}) test.WaitWithDefaultTimeout() + Expect(test.ExitCode()).To(BeZero()) jsonArr := test.OutputToStringArray() Expect(len(jsonArr)).To(Not(BeZero())) eventsMap := make(map[string]string) @@ -143,10 +144,10 @@ var _ = Describe("Podman events", func() { Expect(err).To(BeNil()) _, exist := eventsMap["Status"] Expect(exist).To(BeTrue()) - Expect(test.ExitCode()).To(BeZero()) test = podmanTest.Podman([]string{"events", "--stream=false", "--format", "{{json.}}"}) test.WaitWithDefaultTimeout() + Expect(test.ExitCode()).To(BeZero()) jsonArr = test.OutputToStringArray() Expect(len(jsonArr)).To(Not(BeZero())) eventsMap = make(map[string]string) @@ -154,6 +155,5 @@ var _ = Describe("Podman events", func() { Expect(err).To(BeNil()) _, exist = eventsMap["Status"] Expect(exist).To(BeTrue()) - Expect(test.ExitCode()).To(BeZero()) }) }) diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 23604f47d..4b68f6232 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/run_test.go b/test/e2e/run_test.go index 9d48f1540..7bb474769 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1081,4 +1081,35 @@ USER mail` Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring(limit)) }) + + It("podman run umask", func() { + if !strings.Contains(podmanTest.OCIRuntime, "crun") { + Skip("Test only works on crun") + } + + session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "sh", "-c", "umask"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("0022")) + + session = podmanTest.Podman([]string{"run", "--umask", "0002", "--rm", ALPINE, "sh", "-c", "umask"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("0002")) + + session = podmanTest.Podman([]string{"run", "--umask", "0077", "--rm", fedoraMinimal, "umask"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("0077")) + + session = podmanTest.Podman([]string{"run", "--umask", "22", "--rm", ALPINE, "sh", "-c", "umask"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("0022")) + + session = podmanTest.Podman([]string{"run", "--umask", "9999", "--rm", ALPINE, "sh", "-c", "umask"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session.ErrorToString()).To(ContainSubstring("Invalid umask")) + }) }) diff --git a/test/system/015-help.bats b/test/system/015-help.bats index 3d05b44fe..76d29d22c 100644 --- a/test/system/015-help.bats +++ b/test/system/015-help.bats @@ -78,7 +78,8 @@ function check_help() { if ! expr "$usage" : '.*[A-Z]' >/dev/null; then if [ "$cmd" != "help" ]; then dprint "$command_string invalid-arg" - run_podman 125 "$@" $cmd invalid-arg + run_podman '?' "$@" $cmd invalid-arg + is "$status" 125 "'$command_string invalid-arg' - exit status" is "$output" "Error: .* takes no arguments" \ "'$command_string' with extra (invalid) arguments" fi @@ -104,7 +105,8 @@ function check_help() { # The </dev/null protects us from 'podman login' which will # try to read username/password from stdin. dprint "$command_string (without required args)" - run_podman 125 "$@" $cmd </dev/null + run_podman '?' "$@" $cmd </dev/null + is "$status" 125 "'$command_string' with no arguments - exit status" is "$output" "Error:.* \(require\|specif\|must\|provide\|need\|choose\|accepts\)" \ "'$command_string' without required arg" @@ -126,7 +128,8 @@ function check_help() { local rhs=$(sed -e 's/^[^A-Z]\+[A-Z]/X/' -e 's/ | /-or-/g' <<<"$usage") local n_args=$(wc -w <<<"$rhs") - run_podman 125 "$@" $cmd $(seq --format='x%g' 0 $n_args) + run_podman '?' "$@" $cmd $(seq --format='x%g' 0 $n_args) + is "$status" 125 "'$command_string' with >$n_args arguments - exit status" is "$output" "Error:.* \(takes no arguments\|requires exactly $n_args arg\|accepts at most\|too many arguments\|accepts $n_args arg(s), received\|accepts between .* and .* arg(s), received \)" \ "'$command_string' with >$n_args arguments" @@ -140,13 +143,17 @@ function check_help() { # Any command that takes subcommands, must throw error if called # without one. dprint "podman $@" - run_podman 125 "$@" - is "$output" "Error: missing command .*$@ COMMAND" + run_podman '?' "$@" + is "$status" 125 "'podman $*' without any subcommand - exit status" + is "$output" "Error: missing command .*$@ COMMAND" \ + "'podman $*' without any subcommand - expected error message" # Assume that 'NoSuchCommand' is not a command dprint "podman $@ NoSuchCommand" - run_podman 125 "$@" NoSuchCommand - is "$output" "Error: unrecognized command .*$@ NoSuchCommand" + run_podman '?' "$@" NoSuchCommand + is "$status" 125 "'podman $* NoSuchCommand' - exit status" + is "$output" "Error: unrecognized command .*$@ NoSuchCommand" \ + "'podman $* NoSuchCommand' - expected error message" # This can happen if the output of --help changes, such as between # the old command parser and cobra. diff --git a/vendor/github.com/opencontainers/runtime-tools/generate/generate.go b/vendor/github.com/opencontainers/runtime-tools/generate/generate.go index 6d3268902..c757c20e0 100644 --- a/vendor/github.com/opencontainers/runtime-tools/generate/generate.go +++ b/vendor/github.com/opencontainers/runtime-tools/generate/generate.go @@ -29,6 +29,9 @@ var ( type Generator struct { Config *rspec.Spec HostSpecific bool + // This is used to keep a cache of the ENVs added to improve + // performance when adding a huge number of ENV variables + envMap map[string]int } // ExportOptions have toggles for exporting only certain parts of the specification @@ -236,7 +239,12 @@ func New(os string) (generator Generator, err error) { } } - return Generator{Config: &config}, nil + envCache := map[string]int{} + if config.Process != nil { + envCache = createEnvCacheMap(config.Process.Env) + } + + return Generator{Config: &config, envMap: envCache}, nil } // NewFromSpec creates a configuration Generator from a given @@ -246,8 +254,14 @@ func New(os string) (generator Generator, err error) { // // generator := Generator{Config: config} func NewFromSpec(config *rspec.Spec) Generator { + envCache := map[string]int{} + if config != nil && config.Process != nil { + envCache = createEnvCacheMap(config.Process.Env) + } + return Generator{ Config: config, + envMap: envCache, } } @@ -273,11 +287,27 @@ func NewFromTemplate(r io.Reader) (Generator, error) { if err := json.NewDecoder(r).Decode(&config); err != nil { return Generator{}, err } + + envCache := map[string]int{} + if config.Process != nil { + envCache = createEnvCacheMap(config.Process.Env) + } + return Generator{ Config: &config, + envMap: envCache, }, nil } +// createEnvCacheMap creates a hash map with the ENV variables given by the config +func createEnvCacheMap(env []string) map[string]int { + envMap := make(map[string]int, len(env)) + for i, val := range env { + envMap[val] = i + } + return envMap +} + // SetSpec sets the configuration in the Generator g. // // Deprecated: Replace with: @@ -414,6 +444,12 @@ func (g *Generator) SetProcessUsername(username string) { g.Config.Process.User.Username = username } +// SetProcessUmask sets g.Config.Process.User.Umask. +func (g *Generator) SetProcessUmask(umask uint32) { + g.initConfigProcess() + g.Config.Process.User.Umask = umask +} + // SetProcessGID sets g.Config.Process.User.GID. func (g *Generator) SetProcessGID(gid uint32) { g.initConfigProcess() @@ -456,21 +492,44 @@ func (g *Generator) ClearProcessEnv() { return } g.Config.Process.Env = []string{} + // Clear out the env cache map as well + g.envMap = map[string]int{} } // AddProcessEnv adds name=value into g.Config.Process.Env, or replaces an // existing entry with the given name. func (g *Generator) AddProcessEnv(name, value string) { + if name == "" { + return + } + g.initConfigProcess() + g.addEnv(fmt.Sprintf("%s=%s", name, value), name) +} - env := fmt.Sprintf("%s=%s", name, value) - for idx := range g.Config.Process.Env { - if strings.HasPrefix(g.Config.Process.Env[idx], name+"=") { - g.Config.Process.Env[idx] = env - return - } +// AddMultipleProcessEnv adds multiple name=value into g.Config.Process.Env, or replaces +// existing entries with the given name. +func (g *Generator) AddMultipleProcessEnv(envs []string) { + g.initConfigProcess() + + for _, val := range envs { + split := strings.SplitN(val, "=", 2) + g.addEnv(val, split[0]) + } +} + +// addEnv looks through adds ENV to the Process and checks envMap for +// any duplicates +// This is called by both AddMultipleProcessEnv and AddProcessEnv +func (g *Generator) addEnv(env, key string) { + if idx, ok := g.envMap[key]; ok { + // The ENV exists in the cache, so change its value in g.Config.Process.Env + g.Config.Process.Env[idx] = env + } else { + // else the env doesn't exist, so add it and add it's index to g.envMap + g.Config.Process.Env = append(g.Config.Process.Env, env) + g.envMap[key] = len(g.Config.Process.Env) - 1 } - g.Config.Process.Env = append(g.Config.Process.Env, env) } // AddProcessRlimits adds rlimit into g.Config.Process.Rlimits. @@ -1443,7 +1502,7 @@ func (g *Generator) AddDevice(device rspec.LinuxDevice) { return } if dev.Type == device.Type && dev.Major == device.Major && dev.Minor == device.Minor { - fmt.Fprintln(os.Stderr, "WARNING: The same type, major and minor should not be used for multiple devices.") + fmt.Fprintf(os.Stderr, "WARNING: Creating device %q with same type, major and minor as existing %q.\n", device.Path, dev.Path) } } diff --git a/vendor/github.com/opencontainers/runtime-tools/generate/seccomp/seccomp_default.go b/vendor/github.com/opencontainers/runtime-tools/generate/seccomp/seccomp_default.go index 5fee5a3b2..8a8dc3970 100644 --- a/vendor/github.com/opencontainers/runtime-tools/generate/seccomp/seccomp_default.go +++ b/vendor/github.com/opencontainers/runtime-tools/generate/seccomp/seccomp_default.go @@ -566,6 +566,20 @@ func DefaultProfile(rs *specs.Spec) *rspec.LinuxSeccomp { }, }...) /* Flags parameter of the clone syscall is the 2nd on s390 */ + syscalls = append(syscalls, []rspec.LinuxSyscall{ + { + Names: []string{"clone"}, + Action: rspec.ActAllow, + Args: []rspec.LinuxSeccompArg{ + { + Index: 1, + Value: 2080505856, + ValueTwo: 0, + Op: rspec.OpMaskedEqual, + }, + }, + }, + }...) } return &rspec.LinuxSeccomp{ diff --git a/vendor/modules.txt b/vendor/modules.txt index 913cb71eb..4d10cd5b8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -421,7 +421,7 @@ github.com/opencontainers/runc/libcontainer/user github.com/opencontainers/runc/libcontainer/utils # github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2 github.com/opencontainers/runtime-spec/specs-go -# github.com/opencontainers/runtime-tools v0.9.0 +# github.com/opencontainers/runtime-tools v0.9.1-0.20200714183735-07406c5828aa github.com/opencontainers/runtime-tools/error github.com/opencontainers/runtime-tools/filepath github.com/opencontainers/runtime-tools/generate |