diff options
46 files changed, 707 insertions, 333 deletions
diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 83a25f4ab..f4fecf4b7 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -1,6 +1,15 @@ package common -import "github.com/containers/podman/v2/pkg/domain/entities" +import ( + "fmt" + "net" + "strconv" + "strings" + + "github.com/containers/podman/v2/pkg/api/handlers" + "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/specgen" +) type ContainerCLIOpts struct { Annotation []string @@ -111,3 +120,283 @@ type ContainerCLIOpts struct { CgroupConf []string } + +func stringMaptoArray(m map[string]string) []string { + a := make([]string, 0, len(m)) + for k, v := range m { + a = append(a, fmt.Sprintf("%s=%s", k, v)) + } + return a +} + +// ContainerCreateToContainerCLIOpts converts a compat input struct to cliopts so it can be converted to +// a specgen spec. +func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig) (*ContainerCLIOpts, []string, error) { + var ( + capAdd []string + cappDrop []string + entrypoint string + init bool + specPorts []specgen.PortMapping + ) + + if cc.HostConfig.Init != nil { + init = *cc.HostConfig.Init + } + + // Iterate devices and convert back to string + devices := make([]string, 0, len(cc.HostConfig.Devices)) + for _, dev := range cc.HostConfig.Devices { + devices = append(devices, fmt.Sprintf("%s:%s:%s", dev.PathOnHost, dev.PathInContainer, dev.CgroupPermissions)) + } + + // iterate blkreaddevicebps + readBps := make([]string, 0, len(cc.HostConfig.BlkioDeviceReadBps)) + for _, dev := range cc.HostConfig.BlkioDeviceReadBps { + readBps = append(readBps, dev.String()) + } + + // iterate blkreaddeviceiops + readIops := make([]string, 0, len(cc.HostConfig.BlkioDeviceReadIOps)) + for _, dev := range cc.HostConfig.BlkioDeviceReadIOps { + readIops = append(readIops, dev.String()) + } + + // iterate blkwritedevicebps + writeBps := make([]string, 0, len(cc.HostConfig.BlkioDeviceWriteBps)) + for _, dev := range cc.HostConfig.BlkioDeviceWriteBps { + writeBps = append(writeBps, dev.String()) + } + + // iterate blkwritedeviceiops + writeIops := make([]string, 0, len(cc.HostConfig.BlkioDeviceWriteIOps)) + for _, dev := range cc.HostConfig.BlkioDeviceWriteIOps { + writeIops = append(writeIops, dev.String()) + } + + // entrypoint + // can be a string or slice. if it is a slice, we need to + // marshall it to json; otherwise it should just be the string + // value + if len(cc.Config.Entrypoint) > 0 { + entrypoint = cc.Config.Entrypoint[0] + if len(cc.Config.Entrypoint) > 1 { + b, err := json.Marshal(cc.Config.Entrypoint) + if err != nil { + return nil, nil, err + } + entrypoint = string(b) + } + } + + // expose ports + expose := make([]string, 0, len(cc.Config.ExposedPorts)) + for p := range cc.Config.ExposedPorts { + expose = append(expose, fmt.Sprintf("%s/%s", p.Port(), p.Proto())) + } + + // mounts type=tmpfs/bind,source=,dest=,opt=val + // TODO options + mounts := make([]string, 0, len(cc.HostConfig.Mounts)) + for _, m := range cc.HostConfig.Mounts { + mount := fmt.Sprintf("type=%s", m.Type) + if len(m.Source) > 0 { + mount += fmt.Sprintf("source=%s", m.Source) + } + if len(m.Target) > 0 { + mount += fmt.Sprintf("dest=%s", m.Target) + } + mounts = append(mounts, mount) + } + + //volumes + volumes := make([]string, 0, len(cc.Config.Volumes)) + for v := range cc.Config.Volumes { + volumes = append(volumes, v) + } + + // dns + dns := make([]net.IP, 0, len(cc.HostConfig.DNS)) + for _, d := range cc.HostConfig.DNS { + dns = append(dns, net.ParseIP(d)) + } + + // publish + for port, pbs := range cc.HostConfig.PortBindings { + for _, pb := range pbs { + hostport, err := strconv.Atoi(pb.HostPort) + if err != nil { + return nil, nil, err + } + tmpPort := specgen.PortMapping{ + HostIP: pb.HostIP, + ContainerPort: uint16(port.Int()), + HostPort: uint16(hostport), + Range: 0, + Protocol: port.Proto(), + } + specPorts = append(specPorts, tmpPort) + } + } + + // network names + endpointsConfig := cc.NetworkingConfig.EndpointsConfig + cniNetworks := make([]string, 0, len(endpointsConfig)) + for netName := range endpointsConfig { + cniNetworks = append(cniNetworks, netName) + } + + // netMode + nsmode, _, err := specgen.ParseNetworkNamespace(cc.HostConfig.NetworkMode.NetworkName()) + if err != nil { + return nil, nil, err + } + + netNS := specgen.Namespace{ + NSMode: nsmode.NSMode, + Value: nsmode.Value, + } + + // network + // Note: we cannot emulate compat exactly here. we only allow specifics of networks to be + // defined when there is only one network. + netInfo := entities.NetOptions{ + AddHosts: cc.HostConfig.ExtraHosts, + CNINetworks: cniNetworks, + DNSOptions: cc.HostConfig.DNSOptions, + DNSSearch: cc.HostConfig.DNSSearch, + DNSServers: dns, + Network: netNS, + PublishPorts: specPorts, + } + + // static IP and MAC + if len(endpointsConfig) == 1 { + for _, ep := range endpointsConfig { + // if IP address is provided + if len(ep.IPAddress) > 0 { + staticIP := net.ParseIP(ep.IPAddress) + netInfo.StaticIP = &staticIP + } + // If MAC address is provided + if len(ep.MacAddress) > 0 { + staticMac, err := net.ParseMAC(ep.MacAddress) + if err != nil { + return nil, nil, err + } + netInfo.StaticMAC = &staticMac + } + break + } + } + + // Note: several options here are marked as "don't need". this is based + // on speculation by Matt and I. We think that these come into play later + // like with start. We believe this is just a difference in podman/compat + cliOpts := ContainerCLIOpts{ + //Attach: nil, // dont need? + Authfile: "", + BlkIOWeight: strconv.Itoa(int(cc.HostConfig.BlkioWeight)), + BlkIOWeightDevice: nil, // TODO + CapAdd: append(capAdd, cc.HostConfig.CapAdd...), + CapDrop: append(cappDrop, cc.HostConfig.CapDrop...), + CGroupParent: cc.HostConfig.CgroupParent, + CIDFile: cc.HostConfig.ContainerIDFile, + CPUPeriod: uint64(cc.HostConfig.CPUPeriod), + CPUQuota: cc.HostConfig.CPUQuota, + CPURTPeriod: uint64(cc.HostConfig.CPURealtimePeriod), + CPURTRuntime: cc.HostConfig.CPURealtimeRuntime, + CPUShares: uint64(cc.HostConfig.CPUShares), + //CPUS: 0, // dont need? + CPUSetCPUs: cc.HostConfig.CpusetCpus, + CPUSetMems: cc.HostConfig.CpusetMems, + //Detach: false, // dont need + //DetachKeys: "", // dont need + Devices: devices, + DeviceCGroupRule: nil, + DeviceReadBPs: readBps, + DeviceReadIOPs: readIops, + DeviceWriteBPs: writeBps, + DeviceWriteIOPs: writeIops, + Entrypoint: &entrypoint, + Env: cc.Config.Env, + Expose: expose, + GroupAdd: cc.HostConfig.GroupAdd, + Hostname: cc.Config.Hostname, + ImageVolume: "bind", + Init: init, + Interactive: cc.Config.OpenStdin, + IPC: string(cc.HostConfig.IpcMode), + Label: stringMaptoArray(cc.Config.Labels), + LogDriver: cc.HostConfig.LogConfig.Type, + LogOptions: stringMaptoArray(cc.HostConfig.LogConfig.Config), + Memory: strconv.Itoa(int(cc.HostConfig.Memory)), + MemoryReservation: strconv.Itoa(int(cc.HostConfig.MemoryReservation)), + MemorySwap: strconv.Itoa(int(cc.HostConfig.MemorySwap)), + Name: cc.Name, + OOMScoreAdj: cc.HostConfig.OomScoreAdj, + OverrideArch: "", + OverrideOS: "", + OverrideVariant: "", + PID: string(cc.HostConfig.PidMode), + PIDsLimit: cc.HostConfig.PidsLimit, + Privileged: cc.HostConfig.Privileged, + PublishAll: cc.HostConfig.PublishAllPorts, + Quiet: false, + ReadOnly: cc.HostConfig.ReadonlyRootfs, + ReadOnlyTmpFS: true, // podman default + Rm: cc.HostConfig.AutoRemove, + SecurityOpt: cc.HostConfig.SecurityOpt, + ShmSize: strconv.Itoa(int(cc.HostConfig.ShmSize)), + StopSignal: cc.Config.StopSignal, + StoreageOpt: stringMaptoArray(cc.HostConfig.StorageOpt), + Sysctl: stringMaptoArray(cc.HostConfig.Sysctls), + Systemd: "true", // podman default + TmpFS: stringMaptoArray(cc.HostConfig.Tmpfs), + TTY: cc.Config.Tty, + //Ulimit: cc.HostConfig.Ulimits, // ask dan, no documented format + User: cc.Config.User, + UserNS: string(cc.HostConfig.UsernsMode), + UTS: string(cc.HostConfig.UTSMode), + Mount: mounts, + Volume: volumes, + VolumesFrom: cc.HostConfig.VolumesFrom, + Workdir: cc.Config.WorkingDir, + Net: &netInfo, + } + + if cc.Config.StopTimeout != nil { + cliOpts.StopTimeout = uint(*cc.Config.StopTimeout) + } + + if cc.HostConfig.KernelMemory > 0 { + cliOpts.KernelMemory = strconv.Itoa(int(cc.HostConfig.KernelMemory)) + } + if len(cc.HostConfig.RestartPolicy.Name) > 0 { + policy := cc.HostConfig.RestartPolicy.Name + // only add restart count on failure + if cc.HostConfig.RestartPolicy.IsOnFailure() { + policy += fmt.Sprintf(":%d", cc.HostConfig.RestartPolicy.MaximumRetryCount) + } + cliOpts.Restart = policy + } + + if cc.HostConfig.MemorySwappiness != nil { + cliOpts.MemorySwappiness = *cc.HostConfig.MemorySwappiness + } + if cc.HostConfig.OomKillDisable != nil { + cliOpts.OOMKillDisable = *cc.HostConfig.OomKillDisable + } + if cc.Config.Healthcheck != nil { + cliOpts.HealthCmd = strings.Join(cc.Config.Healthcheck.Test, " ") + cliOpts.HealthInterval = cc.Config.Healthcheck.Interval.String() + cliOpts.HealthRetries = uint(cc.Config.Healthcheck.Retries) + cliOpts.HealthStartPeriod = cc.Config.Healthcheck.StartPeriod.String() + cliOpts.HealthTimeout = cc.Config.Healthcheck.Timeout.String() + } + + // specgen assumes the image name is arg[0] + cmd := []string{cc.Image} + cmd = append(cmd, cc.Config.Cmd...) + return &cliOpts, cmd, nil +} diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 809162e76..eb96e22b5 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -63,7 +63,6 @@ func createFlags(flags *pflag.FlagSet) { _ = flags.MarkHidden("signature-policy") if registry.IsRemote() { - _ = flags.MarkHidden("authfile") _ = flags.MarkHidden("http-proxy") } } diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 19c984bbf..cca0e8d1c 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -66,7 +66,6 @@ func runFlags(flags *pflag.FlagSet) { _ = flags.MarkHidden("signature-policy") if registry.IsRemote() { - _ = flags.MarkHidden("authfile") _ = flags.MarkHidden("http-proxy") _ = flags.MarkHidden("preserve-fds") } diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index 448543b4d..595a2165e 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -84,11 +84,11 @@ func pullFlags(flags *pflag.FlagSet) { 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") + flags.StringVar(&pullOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") if !registry.IsRemote() { - flags.StringVar(&pullOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.StringVar(&pullOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") - flags.BoolVar(&pullOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") } _ = flags.MarkHidden("signature-policy") } diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index 2943cccc9..24192eccd 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -88,11 +88,9 @@ func pushFlags(flags *pflag.FlagSet) { flags.BoolVar(&pushOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") if registry.IsRemote() { - _ = flags.MarkHidden("authfile") _ = flags.MarkHidden("cert-dir") _ = flags.MarkHidden("compress") _ = flags.MarkHidden("quiet") - _ = flags.MarkHidden("tls-verify") } _ = flags.MarkHidden("signature-policy") } diff --git a/cmd/podman/manifest/add.go b/cmd/podman/manifest/add.go index 128bf66a7..544d99c11 100644 --- a/cmd/podman/manifest/add.go +++ b/cmd/podman/manifest/add.go @@ -56,9 +56,7 @@ func init() { flags.StringVar(&manifestAddOpts.Variant, "variant", "", "override the `Variant` of the specified image") if registry.IsRemote() { - _ = flags.MarkHidden("authfile") _ = flags.MarkHidden("cert-dir") - _ = flags.MarkHidden("tls-verify") } } diff --git a/cmd/podman/manifest/push.go b/cmd/podman/manifest/push.go index b43e4531a..fd3d51f37 100644 --- a/cmd/podman/manifest/push.go +++ b/cmd/podman/manifest/push.go @@ -51,9 +51,7 @@ func init() { flags.BoolVarP(&manifestPushOpts.Quiet, "quiet", "q", false, "don't output progress information when pushing lists") if registry.IsRemote() { - _ = flags.MarkHidden("authfile") _ = flags.MarkHidden("cert-dir") - _ = flags.MarkHidden("tls-verify") } } diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index 976d720ee..1235f75a8 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -54,10 +54,10 @@ func init() { flags.StringVar(&kubeOptions.CredentialsCLI, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") flags.StringVar(&kubeOptions.Network, "network", "", "Connect pod to CNI network(s)") flags.BoolVarP(&kubeOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images") + flags.BoolVar(&kubeOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + flags.StringVar(&kubeOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") if !registry.IsRemote() { - flags.StringVar(&kubeOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.StringVar(&kubeOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") - flags.BoolVar(&kubeOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") flags.StringVar(&kubeOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") flags.StringVar(&kubeOptions.SeccompProfileRoot, "seccomp-profile-root", defaultSeccompRoot, "Directory path for seccomp profiles") flags.StringSliceVar(&kubeOptions.ConfigMaps, "configmap", []string{}, "`Pathname` of a YAML file containing a kubernetes configmap") diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 1b992711f..3135a5e65 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -52,7 +52,7 @@ case "$CG_FS_TYPE" in if [[ "$OS_RELEASE_ID" == "ubuntu" ]]; then echo "export OCI_RUNTIME=/usr/lib/cri-o-runc/sbin/runc" >> /etc/environment else - echo "export OCI_RUNTIME=/usr/bin/runc" >> /etc/environment + echo "export OCI_RUNTIME=runc" >> /etc/environment fi fi ;; @@ -61,7 +61,7 @@ case "$CG_FS_TYPE" in # This is necessary since we've built/installed from source, # which uses runc as the default. warn "Forcing testing with crun instead of runc" - echo "export OCI_RUNTIME=/usr/bin/crun" >> /etc/environment + echo "export OCI_RUNTIME=crun" >> /etc/environment fi ;; *) die_unknown CG_FS_TYPE diff --git a/docs/source/markdown/podman-auto-update.1.md b/docs/source/markdown/podman-auto-update.1.md index b6941362a..3ab097388 100644 --- a/docs/source/markdown/podman-auto-update.1.md +++ b/docs/source/markdown/podman-auto-update.1.md @@ -31,7 +31,7 @@ Systemd units that start and stop a container cannot run a new image. **--authfile**=*path* Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`. -If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. (Not available for remote commands) +If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE environment variable. `export REGISTRY_AUTH_FILE=path` diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md index 7d0aa5001..821324f84 100644 --- a/docs/source/markdown/podman-build.1.md +++ b/docs/source/markdown/podman-build.1.md @@ -46,7 +46,7 @@ Set the ARCH of the image to the provided value instead of the architecture of t **--authfile**=*path* Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`. -If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. (Not available for remote commands) +If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE environment variable. `export REGISTRY_AUTH_FILE=path` @@ -470,7 +470,7 @@ When --timestamp is set, the created timestamp is always set to the time specifi **--tls-verify**=*true|false* -Require HTTPS and verify certificates when talking to container registries (defaults to true). (Not available for remote commands) +Require HTTPS and verify certificates when talking to container registries (defaults to true). **--ulimit**=*type*=*soft-limit*[:*hard-limit*] diff --git a/docs/source/markdown/podman-container-runlabel.1.md b/docs/source/markdown/podman-container-runlabel.1.md index f56fc7d7b..676ad12d0 100644 --- a/docs/source/markdown/podman-container-runlabel.1.md +++ b/docs/source/markdown/podman-container-runlabel.1.md @@ -44,7 +44,7 @@ Any additional arguments will be appended to the command. **--authfile**=*path* Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`. -If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. (Not available for remote commands) +If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE environment variable. `export REGISTRY_AUTH_FILE=path` @@ -85,7 +85,7 @@ created from this image. Require HTTPS and verify certificates when contacting registries (default: true). If explicitly set to true, then TLS verification will be used. If set to false, then TLS verification will not be used. If not specified, -TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf (Not available for remote commands) +TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf. ## EXAMPLES diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 3303ae572..f9a23d314 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -47,7 +47,7 @@ each of stdin, stdout, and stderr. Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE -environment variable. `export REGISTRY_AUTH_FILE=path` (Not available for remote commands) +environment variable. `export REGISTRY_AUTH_FILE=path` **--blkio-weight**=*weight* diff --git a/docs/source/markdown/podman-login.1.md b/docs/source/markdown/podman-login.1.md index 9b4ff74ed..7c09d99fe 100644 --- a/docs/source/markdown/podman-login.1.md +++ b/docs/source/markdown/podman-login.1.md @@ -42,7 +42,7 @@ Username for registry **--authfile**=*path* -Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json (Not available for remote commands) +Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json. Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE environment variable. `export REGISTRY_AUTH_FILE=path` @@ -60,7 +60,7 @@ Default certificates directory is _/etc/containers/certs.d_. (Not available for Require HTTPS and verify certificates when contacting registries (default: true). If explicitly set to true, then TLS verification will be used. If set to false, then TLS verification will not be used. If not specified, -TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf. (Not available for remote commands) +TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf. **--help**, **-h** diff --git a/docs/source/markdown/podman-logout.1.md b/docs/source/markdown/podman-logout.1.md index 0ff954d43..25f6d97b1 100644 --- a/docs/source/markdown/podman-logout.1.md +++ b/docs/source/markdown/podman-logout.1.md @@ -23,7 +23,7 @@ All the cached credentials can be removed by setting the **all** flag. **--authfile**=*path* -Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json (Not available for remote commands) +Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json. Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE environment variable. `export REGISTRY_AUTH_FILE=path` diff --git a/docs/source/markdown/podman-manifest-add.1.md b/docs/source/markdown/podman-manifest-add.1.md index c4d4417c4..4c0bad2ae 100644 --- a/docs/source/markdown/podman-manifest-add.1.md +++ b/docs/source/markdown/podman-manifest-add.1.md @@ -36,7 +36,7 @@ retrieved from the image's configuration information. **--authfile**=*path* Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`. -If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. (Not available for remote commands) +If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE environment variable. `export REGISTRY_AUTH_FILE=path` @@ -71,7 +71,7 @@ image. This option is rarely used. **--tls-verify** -Require HTTPS and verify certificates when talking to container registries (defaults to true). (Not available for remote commands) +Require HTTPS and verify certificates when talking to container registries (defaults to true). **--variant** diff --git a/docs/source/markdown/podman-manifest-push.1.md b/docs/source/markdown/podman-manifest-push.1.md index 33b2a24c5..9cf0b159c 100644 --- a/docs/source/markdown/podman-manifest-push.1.md +++ b/docs/source/markdown/podman-manifest-push.1.md @@ -22,7 +22,7 @@ the list or index itself. **--authfile**=*path* Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`. -If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. (Not available for remote commands) +If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE environment variable. `export REGISTRY_AUTH_FILE=path` @@ -64,7 +64,7 @@ Sign the pushed images using the GPG key that matches the specified fingerprint. **--tls-verify** -Require HTTPS and verify certificates when talking to container registries (defaults to true) (Not available for remote commands) +Require HTTPS and verify certificates when talking to container registries. (defaults to true) ## EXAMPLE diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md index 00ee7c1df..97b0dc09a 100644 --- a/docs/source/markdown/podman-play-kube.1.md +++ b/docs/source/markdown/podman-play-kube.1.md @@ -20,7 +20,7 @@ Note: HostPath volume types created by play kube will be given an SELinux privat **--authfile**=*path* Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`. -If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. (Not available for remote commands) +If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE environment variable. `export REGISTRY_AUTH_FILE=path` @@ -58,7 +58,7 @@ Directory path for seccomp profiles (default: "/var/lib/kubelet/seccomp"). (Not Require HTTPS and verify certificates when contacting registries (default: true). If explicitly set to true, then TLS verification will be used. If set to false, then TLS verification will not be used. If not specified, -TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf. (Not available for remote commands) +TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf. **--help**, **-h** diff --git a/docs/source/markdown/podman-pull.1.md b/docs/source/markdown/podman-pull.1.md index c75cb18b4..46beb4c42 100644 --- a/docs/source/markdown/podman-pull.1.md +++ b/docs/source/markdown/podman-pull.1.md @@ -57,7 +57,7 @@ Note: When using the all-tags flag, Podman will not iterate over the search regi **--authfile**=*path* Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`. -If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. (Not available for remote commands) +If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE environment variable. `export REGISTRY_AUTH_FILE=path` @@ -97,7 +97,7 @@ Suppress output information when pulling images Require HTTPS and verify certificates when contacting registries (default: true). If explicitly set to true, then TLS verification will be used. If set to false, then TLS verification will not be used. If not specified, -TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf. (Not available for remote commands) +TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf. **--help**, **-h** diff --git a/docs/source/markdown/podman-push.1.md b/docs/source/markdown/podman-push.1.md index fffd76801..e9b63dc43 100644 --- a/docs/source/markdown/podman-push.1.md +++ b/docs/source/markdown/podman-push.1.md @@ -46,7 +46,7 @@ Images are pushed from those stored in local image storage. **--authfile**=*path* Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`. -If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. (Not available for remote commands) +If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE environment variable. `export REGISTRY_AUTH_FILE=path` @@ -98,7 +98,7 @@ Add a signature at the destination using the specified key Require HTTPS and verify certificates when contacting registries (default: true). If explicitly set to true, then TLS verification will be used. If set to false, then TLS verification will not be used. If not specified, -TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf. (Not available for remote commands) +TLS verification will be used unless the target registry is listed as an insecure registry in registries.conf. ## EXAMPLE diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 9ebf6649b..2c8aa3a26 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -57,7 +57,7 @@ each of **stdin**, **stdout**, and **stderr**. **--authfile**[=*path*] -Path to the authentication file. Default is *${XDG_RUNTIME_DIR}/containers/auth.json*. (Not available for remote commands) +Path to the authentication file. Default is *${XDG_RUNTIME_DIR}/containers/auth.json*. Note: You can also override the default path of the authentication file by setting the **REGISTRY_AUTH_FILE** environment variable. diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index 9be753d26..e195ca314 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -2,7 +2,7 @@ package libpod import ( "bytes" - "path/filepath" + "os" "runtime" "strings" @@ -400,14 +400,30 @@ func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt. // Handle legacy containers which might use a literal path for // their OCI runtime name. runtimeName := ctr.config.OCIRuntime - if strings.HasPrefix(runtimeName, "/") { - runtimeName = filepath.Base(runtimeName) - } - ociRuntime, ok := s.runtime.ociRuntimes[runtimeName] if !ok { - // Use a MissingRuntime implementation - ociRuntime = getMissingRuntime(runtimeName, s.runtime) + runtimeSet := false + + // If the path starts with a / and exists, make a new + // OCI runtime for it using the full path. + if strings.HasPrefix(runtimeName, "/") { + if stat, err := os.Stat(runtimeName); err == nil && !stat.IsDir() { + newOCIRuntime, err := newConmonOCIRuntime(runtimeName, []string{runtimeName}, s.runtime.conmonPath, s.runtime.runtimeFlags, s.runtime.config) + if err == nil { + // The runtime lock should + // protect against concurrent + // modification of the map. + ociRuntime = newOCIRuntime + s.runtime.ociRuntimes[runtimeName] = ociRuntime + runtimeSet = true + } + } + } + + if !runtimeSet { + // Use a MissingRuntime implementation + ociRuntime = getMissingRuntime(runtimeName, s.runtime) + } } ctr.ociRuntime = ociRuntime } diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index eff390e46..ffb2f5b73 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -1541,11 +1541,31 @@ func (c *Container) getHosts() string { } } - if c.config.NetMode.IsSlirp4netns() { - // When using slirp4netns, the interface gets a static IP - hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", "10.0.2.100", c.Hostname(), c.Config().Name) - } hosts += c.cniHosts() + + // If not making a network namespace, add our own hostname. + if c.Hostname() != "" { + if c.config.NetMode.IsSlirp4netns() { + // When using slirp4netns, the interface gets a static IP + hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", "10.0.2.100", c.Hostname(), c.config.Name) + } else { + hasNetNS := false + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.NetworkNamespace { + hasNetNS = true + break + } + } + if !hasNetNS { + // 127.0.1.1 and host's hostname to match Docker + osHostname, err := os.Hostname() + if err != nil { + osHostname = c.Hostname() + } + hosts += fmt.Sprintf("127.0.1.1 %s\n", osHostname) + } + } + } return hosts } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index f87c311ce..df0ff6c32 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -823,6 +823,20 @@ func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) { // Produce an InspectNetworkSettings containing information on the container // network. func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, error) { + if c.config.NetNsCtr != "" { + netNsCtr, err := c.runtime.GetContainer(c.config.NetNsCtr) + if err != nil { + return nil, err + } + // Have to sync to ensure that state is populated + if err := netNsCtr.syncContainer(); err != nil { + return nil, err + } + logrus.Debugf("Container %s shares network namespace, retrieving network info of container %s", c.ID(), c.config.NetNsCtr) + + return netNsCtr.getContainerNetworkInfo() + } + settings := new(define.InspectNetworkSettings) settings.Ports = makeInspectPortBindings(c.config.PortMappings) diff --git a/libpod/runtime.go b/libpod/runtime.go index 7da8b181f..792492db6 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -17,6 +17,7 @@ import ( "github.com/containers/podman/v2/libpod/events" "github.com/containers/podman/v2/libpod/image" "github.com/containers/podman/v2/libpod/lock" + "github.com/containers/podman/v2/libpod/shutdown" "github.com/containers/podman/v2/pkg/cgroups" "github.com/containers/podman/v2/pkg/registries" "github.com/containers/podman/v2/pkg/rootless" @@ -174,9 +175,21 @@ func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...R } } + if err := shutdown.Start(); err != nil { + return nil, errors.Wrapf(err, "error starting shutdown signal handler") + } + if err := makeRuntime(ctx, runtime); err != nil { return nil, err } + + if err := shutdown.Register("libpod", func(sig os.Signal) error { + os.Exit(1) + return nil + }); err != nil { + logrus.Errorf("Error registering shutdown handler for libpod: %v", err) + } + return runtime, nil } @@ -383,14 +396,12 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) { // If the string starts with / it's a path to a runtime // executable. if strings.HasPrefix(runtime.config.Engine.OCIRuntime, "/") { - name := filepath.Base(runtime.config.Engine.OCIRuntime) - - ociRuntime, err := newConmonOCIRuntime(name, []string{runtime.config.Engine.OCIRuntime}, runtime.conmonPath, runtime.runtimeFlags, runtime.config) + ociRuntime, err := newConmonOCIRuntime(runtime.config.Engine.OCIRuntime, []string{runtime.config.Engine.OCIRuntime}, runtime.conmonPath, runtime.runtimeFlags, runtime.config) if err != nil { return err } - runtime.ociRuntimes[name] = ociRuntime + runtime.ociRuntimes[runtime.config.Engine.OCIRuntime] = ociRuntime runtime.defaultOCIRuntime = ociRuntime } else { ociRuntime, ok := runtime.ociRuntimes[runtime.config.Engine.OCIRuntime] diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 51b4c5f03..de73a9ff3 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -12,6 +12,7 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/events" + "github.com/containers/podman/v2/libpod/shutdown" "github.com/containers/podman/v2/pkg/cgroups" "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/storage" @@ -149,6 +150,10 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai return nil, err } + // Inhibit shutdown until creation succeeds + shutdown.Inhibit() + defer shutdown.Uninhibit() + // Allocate a lock for the container lock, err := r.lockManager.AllocateLock() if err != nil { diff --git a/libpod/shutdown/handler.go b/libpod/shutdown/handler.go new file mode 100644 index 000000000..87538dec9 --- /dev/null +++ b/libpod/shutdown/handler.go @@ -0,0 +1,131 @@ +package shutdown + +import ( + "os" + "os/signal" + "sync" + "syscall" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var ( + stopped bool + sigChan chan os.Signal + cancelChan chan bool + // Definitions of all on-shutdown handlers + handlers map[string]func(os.Signal) error + // Ordering that on-shutdown handlers will be invoked. + handlerOrder []string + shutdownInhibit sync.RWMutex +) + +// Start begins handling SIGTERM and SIGINT and will run the given on-signal +// handlers when one is called. This can be cancelled by calling Stop(). +func Start() error { + if sigChan != nil { + // Already running, do nothing. + return nil + } + + sigChan = make(chan os.Signal, 1) + cancelChan = make(chan bool, 1) + stopped = false + + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + go func() { + select { + case <-cancelChan: + signal.Stop(sigChan) + close(sigChan) + close(cancelChan) + stopped = true + return + case sig := <-sigChan: + logrus.Infof("Received shutdown signal %v, terminating!", sig) + shutdownInhibit.Lock() + for _, name := range handlerOrder { + handler, ok := handlers[name] + if !ok { + logrus.Errorf("Shutdown handler %s definition not found!", name) + continue + } + logrus.Infof("Invoking shutdown handler %s", name) + if err := handler(sig); err != nil { + logrus.Errorf("Error running shutdown handler %s: %v", name, err) + } + } + shutdownInhibit.Unlock() + return + } + }() + + return nil +} + +// Stop the shutdown signal handler. +func Stop() error { + if cancelChan == nil { + return errors.New("shutdown signal handler has not yet been started") + } + if stopped { + return nil + } + + cancelChan <- true + + return nil +} + +// Temporarily inhibit signals from shutting down Libpod. +func Inhibit() { + shutdownInhibit.RLock() +} + +// Stop inhibiting signals from shutting down Libpod. +func Uninhibit() { + shutdownInhibit.RUnlock() +} + +// Register registers a function that will be executed when Podman is terminated +// by a signal. Handlers are invoked LIFO - the last handler registered is the +// first run. +func Register(name string, handler func(os.Signal) error) error { + if handlers == nil { + handlers = make(map[string]func(os.Signal) error) + } + + if _, ok := handlers[name]; ok { + return errors.Errorf("handler with name %s already exists", name) + } + + handlers[name] = handler + handlerOrder = append([]string{name}, handlerOrder...) + + return nil +} + +// Unregister un-registers a given shutdown handler. +func Unregister(name string) error { + if handlers == nil { + return nil + } + + if _, ok := handlers[name]; !ok { + return nil + } + + delete(handlers, name) + + newOrder := []string{} + for _, checkName := range handlerOrder { + if checkName != name { + newOrder = append(newOrder, checkName) + } + } + handlerOrder = newOrder + + return nil +} diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 8a0b3c922..87c95a24c 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -1,27 +1,19 @@ package compat import ( - "context" "encoding/json" - "fmt" "net/http" - "strings" - "github.com/containers/common/pkg/config" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod/define" - image2 "github.com/containers/podman/v2/libpod/image" "github.com/containers/podman/v2/pkg/api/handlers" "github.com/containers/podman/v2/pkg/api/handlers/utils" - "github.com/containers/podman/v2/pkg/namespaces" - "github.com/containers/podman/v2/pkg/rootless" - "github.com/containers/podman/v2/pkg/signal" - createconfig "github.com/containers/podman/v2/pkg/spec" + "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/domain/infra/abi" "github.com/containers/podman/v2/pkg/specgen" - "github.com/containers/storage" "github.com/gorilla/schema" "github.com/pkg/errors" - "golang.org/x/sys/unix" ) func CreateContainer(w http.ResponseWriter, r *http.Request) { @@ -56,220 +48,27 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()")) return } - containerConfig, err := runtime.GetConfig() + + // Take input structure and convert to cliopts + cliOpts, args, err := common.ContainerCreateToContainerCLIOpts(input) if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "GetConfig()")) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "make cli opts()")) return } - cc, err := makeCreateConfig(r.Context(), containerConfig, input, newImage) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()")) + sg := specgen.NewSpecGenerator(newImage.ID(), cliOpts.RootFS) + if err := common.FillOutSpecGen(sg, cliOpts, args); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "fill out specgen")) return } - cc.Name = query.Name - utils.CreateContainer(r.Context(), w, runtime, &cc) -} - -func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { - var ( - err error - init bool - ) - env := make(map[string]string) - stopSignal := unix.SIGTERM - if len(input.StopSignal) > 0 { - stopSignal, err = signal.ParseSignal(input.StopSignal) - if err != nil { - return createconfig.CreateConfig{}, err - } - } - - workDir, err := newImage.WorkingDir(ctx) + ic := abi.ContainerEngine{Libpod: runtime} + report, err := ic.ContainerCreate(r.Context(), sg) if err != nil { - return createconfig.CreateConfig{}, err - } - if workDir == "" { - workDir = "/" - } - if len(input.WorkingDir) > 0 { - workDir = input.WorkingDir - } - - // Only use image's Cmd when the user does not set the entrypoint - if input.Entrypoint == nil && len(input.Cmd) == 0 { - cmdSlice, err := newImage.Cmd(ctx) - if err != nil { - return createconfig.CreateConfig{}, err - } - input.Cmd = cmdSlice - } - - if input.Entrypoint == nil { - entrypointSlice, err := newImage.Entrypoint(ctx) - if err != nil { - return createconfig.CreateConfig{}, err - } - input.Entrypoint = entrypointSlice - } - - stopTimeout := containerConfig.Engine.StopTimeout - if input.StopTimeout != nil { - stopTimeout = uint(*input.StopTimeout) - } - c := createconfig.CgroupConfig{ - Cgroups: "", // podman - Cgroupns: "", // podman - CgroupParent: "", // podman - CgroupMode: "", // podman - } - security := createconfig.SecurityConfig{ - CapAdd: input.HostConfig.CapAdd, - CapDrop: input.HostConfig.CapDrop, - LabelOpts: nil, // podman - NoNewPrivs: false, // podman - ApparmorProfile: "", // podman - SeccompProfilePath: "", - SecurityOpts: input.HostConfig.SecurityOpt, - Privileged: input.HostConfig.Privileged, - ReadOnlyRootfs: input.HostConfig.ReadonlyRootfs, - ReadOnlyTmpfs: false, // podman-only - Sysctl: input.HostConfig.Sysctls, - } - - var netmode namespaces.NetworkMode - if rootless.IsRootless() { - netmode = namespaces.NetworkMode(specgen.Slirp) - } - - network := createconfig.NetworkConfig{ - DNSOpt: input.HostConfig.DNSOptions, - DNSSearch: input.HostConfig.DNSSearch, - DNSServers: input.HostConfig.DNS, - ExposedPorts: input.ExposedPorts, - HTTPProxy: false, // podman - IP6Address: "", - IPAddress: "", - LinkLocalIP: nil, // docker-only - MacAddress: input.MacAddress, - NetMode: netmode, - Network: input.HostConfig.NetworkMode.NetworkName(), - NetworkAlias: nil, // docker-only now - PortBindings: input.HostConfig.PortBindings, - Publish: nil, // podmanseccompPath - PublishAll: input.HostConfig.PublishAllPorts, - } - - uts := createconfig.UtsConfig{ - UtsMode: namespaces.UTSMode(input.HostConfig.UTSMode), - NoHosts: false, //podman - HostAdd: input.HostConfig.ExtraHosts, - Hostname: input.Hostname, - } - - z := createconfig.UserConfig{ - GroupAdd: input.HostConfig.GroupAdd, - IDMappings: &storage.IDMappingOptions{}, // podman //TODO <--- fix this, - UsernsMode: namespaces.UsernsMode(input.HostConfig.UsernsMode), - User: input.User, - } - pidConfig := createconfig.PidConfig{PidMode: namespaces.PidMode(input.HostConfig.PidMode)} - // TODO: We should check that these binds are all listed in the `Volumes` - // key since it doesn't make sense to define a `Binds` element for a - // container path which isn't defined as a volume - volumes := input.HostConfig.Binds - - // Docker is more flexible about its input where podman throws - // away incorrectly formatted variables so we cannot reuse the - // parsing of the env input - // [Foo Other=one Blank=] - imgEnv, err := newImage.Env(ctx) - if err != nil { - return createconfig.CreateConfig{}, err - } - input.Env = append(imgEnv, input.Env...) - for _, e := range input.Env { - splitEnv := strings.Split(e, "=") - switch len(splitEnv) { - case 0: - continue - case 1: - env[splitEnv[0]] = "" - default: - env[splitEnv[0]] = strings.Join(splitEnv[1:], "=") - } - } - - // format the tmpfs mounts into a []string from map - tmpfs := make([]string, 0, len(input.HostConfig.Tmpfs)) - for k, v := range input.HostConfig.Tmpfs { - tmpfs = append(tmpfs, fmt.Sprintf("%s:%s", k, v)) - } - - if input.HostConfig.Init != nil && *input.HostConfig.Init { - init = true - } - - m := createconfig.CreateConfig{ - Annotations: nil, // podman - Args: nil, - Cgroup: c, - CidFile: "", - ConmonPidFile: "", // podman - Command: input.Cmd, - UserCommand: input.Cmd, // podman - Detach: false, // - // Devices: input.HostConfig.Devices, - Entrypoint: input.Entrypoint, - Env: env, - HealthCheck: nil, // - Init: init, - InitPath: "", // tbd - Image: input.Image, - ImageID: newImage.ID(), - BuiltinImgVolumes: nil, // podman - ImageVolumeType: "", // podman - Interactive: input.OpenStdin, - // IpcMode: input.HostConfig.IpcMode, - Labels: input.Labels, - LogDriver: input.HostConfig.LogConfig.Type, // is this correct - // LogDriverOpt: input.HostConfig.LogConfig.Config, - Name: input.Name, - Network: network, - Pod: "", // podman - PodmanPath: "", // podman - Quiet: false, // front-end only - Resources: createconfig.CreateResourceConfig{MemorySwappiness: -1}, - RestartPolicy: input.HostConfig.RestartPolicy.Name, - Rm: input.HostConfig.AutoRemove, - StopSignal: stopSignal, - StopTimeout: stopTimeout, - Systemd: false, // podman - Tmpfs: tmpfs, - User: z, - Uts: uts, - Tty: input.Tty, - Mounts: nil, // we populate - // MountsFlag: input.HostConfig.Mounts, - NamedVolumes: nil, // we populate - Volumes: volumes, - VolumesFrom: input.HostConfig.VolumesFrom, - WorkDir: workDir, - Rootfs: "", // podman - Security: security, - Syslog: false, // podman - - Pid: pidConfig, + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "container create")) + return } - - 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:] - } + createResponse := entities.ContainerCreateResponse{ + ID: report.Id, + Warnings: []string{}, } - - return m, nil + utils.WriteResponse(w, http.StatusCreated, createResponse) } diff --git a/pkg/api/handlers/compat/images_search.go b/pkg/api/handlers/compat/images_search.go index 131fab69f..b3ceae3ee 100644 --- a/pkg/api/handlers/compat/images_search.go +++ b/pkg/api/handlers/compat/images_search.go @@ -7,6 +7,7 @@ import ( "github.com/containers/image/v5/types" "github.com/containers/podman/v2/libpod/image" "github.com/containers/podman/v2/pkg/api/handlers/utils" + "github.com/containers/podman/v2/pkg/auth" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -14,9 +15,10 @@ import ( func SearchImages(w http.ResponseWriter, r *http.Request) { decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { - Term string `json:"term"` - Limit int `json:"limit"` - Filters map[string][]string `json:"filters"` + Term string `json:"term"` + Limit int `json:"limit"` + Filters map[string][]string `json:"filters"` + TLSVerify bool `json:"tlsVerify"` }{ // This is where you can override the golang default value for one of fields } @@ -58,6 +60,18 @@ func SearchImages(w http.ResponseWriter, r *http.Request) { Limit: query.Limit, } + if _, found := r.URL.Query()["tlsVerify"]; found { + options.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) + } + + _, authfile, key, err := auth.GetCredentials(r) + if err != nil { + utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String())) + return + } + defer auth.RemoveAuthfile(authfile) + options.Authfile = authfile + results, err := image.SearchImages(query.Term, options) if err != nil { utils.BadRequest(w, "term", query.Term, err) diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 1292090fb..3fb5d23c8 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -636,6 +636,14 @@ func SearchImages(w http.ResponseWriter, r *http.Request) { options.Filter = *filter } + _, authfile, key, err := auth.GetCredentials(r) + if err != nil { + utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String())) + return + } + defer auth.RemoveAuthfile(authfile) + options.Authfile = authfile + searchResults, err := image.SearchImages(query.Term, options) if err != nil { utils.BadRequest(w, "term", query.Term, err) diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 355a46fb7..64008767b 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -7,7 +7,6 @@ import ( "net" "net/http" "os" - "os/signal" goRuntime "runtime" "strings" "sync" @@ -15,6 +14,7 @@ import ( "time" "github.com/containers/podman/v2/libpod" + "github.com/containers/podman/v2/libpod/shutdown" "github.com/containers/podman/v2/pkg/api/handlers" "github.com/containers/podman/v2/pkg/api/server/idle" "github.com/coreos/go-systemd/v22/activation" @@ -180,8 +180,17 @@ func setupSystemd() { // Serve starts responding to HTTP requests. func (s *APIServer) Serve() error { setupSystemd() - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + // Start the shutdown signal handler. + if err := shutdown.Start(); err != nil { + return err + } + if err := shutdown.Register("server", func(sig os.Signal) error { + return s.Shutdown() + }); err != nil { + return err + } + errChan := make(chan error, 1) go func() { @@ -217,14 +226,7 @@ func (s *APIServer) Serve() error { errChan <- nil }() - select { - case err := <-errChan: - return err - case sig := <-sigChan: - logrus.Infof("APIServer terminated by signal %v", sig) - } - - return nil + return <-errChan } // Shutdown is a clean shutdown waiting on existing clients diff --git a/pkg/domain/infra/abi/terminal/sigproxy_linux.go b/pkg/domain/infra/abi/terminal/sigproxy_linux.go index f484e926c..0c586cf5c 100644 --- a/pkg/domain/infra/abi/terminal/sigproxy_linux.go +++ b/pkg/domain/infra/abi/terminal/sigproxy_linux.go @@ -5,12 +5,17 @@ import ( "syscall" "github.com/containers/podman/v2/libpod" + "github.com/containers/podman/v2/libpod/shutdown" "github.com/containers/podman/v2/pkg/signal" "github.com/sirupsen/logrus" ) // ProxySignals ... func ProxySignals(ctr *libpod.Container) { + // Stop catching the shutdown signals (SIGINT, SIGTERM) - they're going + // to the container now. + shutdown.Stop() + sigBuffer := make(chan os.Signal, 128) signal.CatchAll(sigBuffer) diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 7adb8be6a..7e4f09dc4 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -342,7 +342,7 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt return errors.Wrapf(err, "error looking up container to share uts namespace with") } hostname = utsCtr.Hostname() - case s.NetNS.NSMode == specgen.Host || s.UtsNS.NSMode == specgen.Host: + case (s.NetNS.NSMode == specgen.Host && hostname == "") || s.UtsNS.NSMode == specgen.Host: tmpHostname, err := os.Hostname() if err != nil { return errors.Wrap(err, "unable to retrieve hostname of the host") diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index f02432f5b..8454458a8 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -110,7 +110,7 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image // Only use image command if the user did not manually set an // entrypoint. command := s.Command - if command == nil && img != nil && s.Entrypoint == nil { + if (command == nil || len(command) == 0) && img != nil && (s.Entrypoint == nil || len(s.Entrypoint) == 0) { newCmd, err := img.Cmd(ctx) if err != nil { return nil, err diff --git a/test/apiv2/01-basic.at b/test/apiv2/01-basic.at index 9d4b04edb..f550d5fc3 100644 --- a/test/apiv2/01-basic.at +++ b/test/apiv2/01-basic.at @@ -59,7 +59,10 @@ t GET info 200 \ .DefaultRuntime~.*$runtime \ .MemTotal~[0-9]\\+ -# Timing: make sure server stays responsive +# Timing: make sure server stays responsive. +# Because /info may need to check storage, it may be slow the first time. +# Let's invoke it once to prime caches, then run ten queries in a timed loop. +t GET info 200 t0=$SECONDS for i in $(seq 1 10); do # FIXME: someday: refactor t(), separate out the 'curl' logic so we @@ -70,7 +73,8 @@ t1=$SECONDS delta_t=$((t1 - t2)) # Desired number of seconds in which we expect to run. -want=7 +# FIXME: 10 seconds is a lot! PR #8076 opened to investigate why. +want=10 if [ $delta_t -le $want ]; then _show_ok 1 "Time for ten /info requests ($delta_t seconds) <= ${want}s" else diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index 7fbcd2e9c..c7055dfc4 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -206,16 +206,6 @@ t POST containers/${cid_top}/stop "" 204 t DELETE containers/$cid 204 t DELETE containers/$cid_top 204 -# test the apiv2 create, shouldn't ignore the ENV and WORKDIR from the image -t POST containers/create '"Image":"'$ENV_WORKDIR_IMG'","Env":["testKey1"]' 201 \ - .Id~[0-9a-f]\\{64\\} -cid=$(jq -r '.Id' <<<"$output") -t GET containers/$cid/json 200 \ - .Config.Env~.*REDIS_VERSION= \ - .Config.Env~.*testKey1= \ - .Config.WorkingDir="/data" # default is /data -t DELETE containers/$cid 204 - # test the WORKDIR and StopSignal t POST containers/create '"Image":"'$ENV_WORKDIR_IMG'","WorkingDir":"/dataDir","StopSignal":"9"' 201 \ .Id~[0-9a-f]\\{64\\} diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2 index 78325eb24..c8ca9df3f 100755 --- a/test/apiv2/test-apiv2 +++ b/test/apiv2/test-apiv2 @@ -179,7 +179,7 @@ function t() { # POST requests require an extra params arg if [[ $method = "POST" ]]; then curl_args="-d $(jsonify $1)" - testname="$testname [$1]" + testname="$testname [$curl_args]" shift fi @@ -204,21 +204,30 @@ function t() { echo "-------------------------------------------------------------" >>$LOG echo "\$ $testname" >>$LOG rm -f $WORKDIR/curl.* - curl -s -X $method ${curl_args} \ - -H 'Content-type: application/json' \ - --dump-header $WORKDIR/curl.headers.out \ - -o $WORKDIR/curl.result.out "$url" - - if [[ $? -eq 7 ]]; then - echo "FATAL: curl failure on $url - cannot continue" >&2 + # -s = silent, but --write-out 'format' gives us important response data + response=$(curl -s -X $method ${curl_args} \ + -H 'Content-type: application/json' \ + --dump-header $WORKDIR/curl.headers.out \ + --write-out '%{http_code}^%{content_type}^%{time_total}' \ + -o $WORKDIR/curl.result.out "$url") + + # Any error from curl is instant bad news, from which we can't recover + rc=$? + if [[ $rc -ne 0 ]]; then + echo "FATAL: curl failure ($rc) on $url - cannot continue" >&2 exit 1 fi - cat $WORKDIR/curl.headers.out >>$LOG 2>/dev/null || true + # Show returned headers (without trailing ^M or empty lines) in log file. + # Sometimes -- I can't remember why! -- we don't get headers. + if [[ -e $WORKDIR/curl.headers.out ]]; then + tr -d '\015' < $WORKDIR/curl.headers.out | egrep '.' >>$LOG + fi - # Log results, if text. If JSON, filter through jq for readability. - content_type=$(sed -ne 's/^Content-Type:[ ]\+//pi' <$WORKDIR/curl.headers.out) + IFS='^' read actual_code content_type time_total <<<"$response" + printf "X-Response-Time: ${time_total}s\n\n" >>$LOG + # Log results, if text. If JSON, filter through jq for readability. if [[ $content_type =~ /octet ]]; then output="[$(file --brief $WORKDIR/curl.result.out)]" echo "$output" >>$LOG @@ -233,10 +242,8 @@ function t() { fi # Test return code - actual_code=$(head -n1 $WORKDIR/curl.headers.out | awk '/^HTTP/ { print $2}') is "$actual_code" "$expected_code" "$testname : status" - # Special case: 204/304, by definition, MUST NOT return content (rfc2616) if [[ $expected_code = 204 || $expected_code = 304 ]]; then if [ -n "$*" ]; then diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index e36c86690..206c66f9f 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -235,14 +235,7 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { ociRuntime := os.Getenv("OCI_RUNTIME") if ociRuntime == "" { - var err error - ociRuntime, err = exec.LookPath("crun") - // If we cannot find the crun binary, setting to something static as we have no way - // to return an error. The tests will fail and point out that the runc binary could - // not be found nicely. - if err != nil { - ociRuntime = "/usr/bin/runc" - } + ociRuntime = "crun" } os.Setenv("DISABLE_HC_SYSTEMD", "true") CNIConfigDir := "/etc/cni/net.d" @@ -673,3 +666,9 @@ func (p *PodmanTestIntegration) PodmanAsUser(args []string, uid, gid uint32, cwd podmanSession := p.PodmanAsUserBase(args, uid, gid, cwd, env, false, false, nil) return &PodmanSessionIntegration{podmanSession} } + +// We don't support running Varlink when local +func (p *PodmanTestIntegration) RestartRemoteService() { + p.StopRemoteService() + p.StartRemoteService() +} diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index f69b6ca7b..83a66d2b9 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -245,6 +245,24 @@ var _ = Describe("Podman pod create", func() { } }) + It("podman container in pod with IP address shares IP address", func() { + SkipIfRootless("Rootless does not support --ip") + podName := "test" + ctrName := "testCtr" + ip := GetRandomIPAddress() + podCreate := podmanTest.Podman([]string{"pod", "create", "--ip", ip, "--name", podName}) + podCreate.WaitWithDefaultTimeout() + Expect(podCreate.ExitCode()).To(Equal(0)) + podCtr := podmanTest.Podman([]string{"run", "--name", ctrName, "--pod", podName, "-d", "-t", ALPINE, "top"}) + podCtr.WaitWithDefaultTimeout() + Expect(podCtr.ExitCode()).To(Equal(0)) + ctrInspect := podmanTest.Podman([]string{"inspect", ctrName}) + ctrInspect.WaitWithDefaultTimeout() + Expect(ctrInspect.ExitCode()).To(Equal(0)) + ctrJSON := ctrInspect.InspectContainerToJSON() + Expect(ctrJSON[0].NetworkSettings.IPAddress).To(Equal(ip)) + }) + It("podman create pod with IP address and no infra should fail", func() { name := "test" ip := GetRandomIPAddress() diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index e14482db7..540ac5409 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -571,4 +571,19 @@ var _ = Describe("Podman run networking", func() { podrm.WaitWithDefaultTimeout() Expect(podrm.ExitCode()).To(BeZero()) }) + + It("podman run net=host adds entry to /etc/hosts", func() { + run := podmanTest.Podman([]string{"run", "--net=host", ALPINE, "cat", "/etc/hosts"}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(BeZero()) + Expect(strings.Contains(run.OutputToString(), "127.0.1.1")).To(BeTrue()) + }) + + It("podman run with --net=host and --hostname sets correct hostname", func() { + hostname := "testctr" + run := podmanTest.Podman([]string{"run", "--net=host", "--hostname", hostname, ALPINE, "hostname"}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(BeZero()) + Expect(strings.Contains(run.OutputToString(), "testctr")).To(BeTrue()) + }) }) diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index 0cf005529..4f2751099 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -237,7 +237,6 @@ registries = ['{{.Host}}:{{.Port}}']` }) It("podman search attempts HTTP if registry is in registries.insecure and force secure is false", func() { - SkipIfRemote("--tls-verify is not supported on podman-remote search") if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } @@ -264,6 +263,10 @@ registries = ['{{.Host}}:{{.Port}}']` registryFileTmpl.Execute(&buffer, registryEndpoints[4]) podmanTest.setRegistriesConfigEnv(buffer.Bytes()) ioutil.WriteFile(fmt.Sprintf("%s/registry4.conf", tempdir), buffer.Bytes(), 0644) + if IsRemote() { + podmanTest.RestartRemoteService() + defer podmanTest.RestartRemoteService() + } search := podmanTest.PodmanNoCache([]string{"search", image}) search.WaitWithDefaultTimeout() @@ -278,7 +281,7 @@ registries = ['{{.Host}}:{{.Port}}']` }) It("podman search doesn't attempt HTTP if force secure is true", func() { - SkipIfRemote("--tls-verify is not supported on podman-remote search") + SkipIfRemote("FIXME This should work on podman-remote") if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } @@ -303,6 +306,10 @@ registries = ['{{.Host}}:{{.Port}}']` registryFileTmpl.Execute(&buffer, registryEndpoints[5]) podmanTest.setRegistriesConfigEnv(buffer.Bytes()) ioutil.WriteFile(fmt.Sprintf("%s/registry5.conf", tempdir), buffer.Bytes(), 0644) + if IsRemote() { + podmanTest.RestartRemoteService() + defer podmanTest.RestartRemoteService() + } search := podmanTest.PodmanNoCache([]string{"search", image, "--tls-verify=true"}) search.WaitWithDefaultTimeout() @@ -317,7 +324,7 @@ registries = ['{{.Host}}:{{.Port}}']` }) It("podman search doesn't attempt HTTP if registry is not listed as insecure", func() { - SkipIfRemote("--tls-verify is not supported on podman-remote search") + SkipIfRemote("FIXME This should work on podman-remote") if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } @@ -343,6 +350,11 @@ registries = ['{{.Host}}:{{.Port}}']` podmanTest.setRegistriesConfigEnv(buffer.Bytes()) ioutil.WriteFile(fmt.Sprintf("%s/registry6.conf", tempdir), buffer.Bytes(), 0644) + if IsRemote() { + podmanTest.RestartRemoteService() + defer podmanTest.RestartRemoteService() + } + search := podmanTest.PodmanNoCache([]string{"search", image}) search.WaitWithDefaultTimeout() @@ -393,6 +405,11 @@ registries = ['{{.Host}}:{{.Port}}']` podmanTest.setRegistriesConfigEnv(buffer.Bytes()) ioutil.WriteFile(fmt.Sprintf("%s/registry8.conf", tempdir), buffer.Bytes(), 0644) + if IsRemote() { + podmanTest.RestartRemoteService() + defer podmanTest.RestartRemoteService() + } + search := podmanTest.PodmanNoCache([]string{"search", "my-alpine"}) search.WaitWithDefaultTimeout() diff --git a/test/e2e/toolbox_test.go b/test/e2e/toolbox_test.go index 4f4113bd4..fbff8d19e 100644 --- a/test/e2e/toolbox_test.go +++ b/test/e2e/toolbox_test.go @@ -222,7 +222,7 @@ var _ = Describe("Toolbox-specific testing", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(WaitContainerReady(podmanTest, "test", "READY", 2, 1)).To(BeTrue()) + Expect(WaitContainerReady(podmanTest, "test", "READY", 5, 1)).To(BeTrue()) expectedOutput := fmt.Sprintf("%s:x:%s:%s::%s:%s", username, uid, gid, homeDir, shell) @@ -257,7 +257,7 @@ var _ = Describe("Toolbox-specific testing", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(WaitContainerReady(podmanTest, "test", "READY", 2, 1)).To(BeTrue()) + Expect(WaitContainerReady(podmanTest, "test", "READY", 5, 1)).To(BeTrue()) session = podmanTest.Podman([]string{"exec", "test", "cat", "/etc/group"}) session.WaitWithDefaultTimeout() @@ -301,7 +301,7 @@ var _ = Describe("Toolbox-specific testing", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(WaitContainerReady(podmanTest, "test", "READY", 2, 1)).To(BeTrue()) + Expect(WaitContainerReady(podmanTest, "test", "READY", 5, 1)).To(BeTrue()) expectedUser := fmt.Sprintf("%s:x:%s:%s::%s:%s", username, uid, gid, homeDir, shell) @@ -358,7 +358,7 @@ var _ = Describe("Toolbox-specific testing", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(WaitContainerReady(podmanTest, "test", "READY", 2, 1)).To(BeTrue()) + Expect(WaitContainerReady(podmanTest, "test", "READY", 5, 1)).To(BeTrue()) session = podmanTest.Podman([]string{"logs", "test"}) session.WaitWithDefaultTimeout() diff --git a/test/endpoint/setup.go b/test/endpoint/setup.go index 56cab06b0..6bbc8d2bc 100644 --- a/test/endpoint/setup.go +++ b/test/endpoint/setup.go @@ -51,14 +51,7 @@ func Setup(tempDir string) *EndpointTestIntegration { ociRuntime := os.Getenv("OCI_RUNTIME") if ociRuntime == "" { - var err error - ociRuntime, err = exec.LookPath("runc") - // If we cannot find the runc binary, setting to something static as we have no way - // to return an error. The tests will fail and point out that the runc binary could - // not be found nicely. - if err != nil { - ociRuntime = "/usr/bin/runc" - } + ociRuntime = "runc" } os.Setenv("DISABLE_HC_SYSTEMD", "true") CNIConfigDir := "/etc/cni/net.d" diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 28dc7c7a7..9f4037730 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -460,4 +460,17 @@ json-file | f is "$output" "$expect" "podman run with --tz=local, matches host" } +# run with --runtime should preserve the named runtime +@test "podman run : full path to --runtime is preserved" { + skip_if_cgroupsv1 + skip_if_remote + run_podman run -d --runtime '/usr/bin/crun' $IMAGE sleep 60 + cid="$output" + + run_podman inspect --format '{{.OCIRuntime}}' $cid + is "$output" "/usr/bin/crun" + + run_podman kill $cid +} + # vim: filetype=sh diff --git a/test/system/260-sdnotify.bats b/test/system/260-sdnotify.bats index 2ddeda96a..c99ba4fa6 100644 --- a/test/system/260-sdnotify.bats +++ b/test/system/260-sdnotify.bats @@ -107,6 +107,7 @@ function _assert_mainpid_is_conmon() { # Done. Stop container, clean up. run_podman exec $cid touch /stop + run_podman wait $cid run_podman rm $cid _stop_socat } @@ -142,6 +143,7 @@ function _assert_mainpid_is_conmon() { # Done. Stop container, clean up. run_podman exec $cid touch /stop + run_podman wait $cid run_podman rm $cid run_podman rmi $_FEDORA _stop_socat diff --git a/test/system/helpers.bash b/test/system/helpers.bash index 73cf1e5b2..2cced10c2 100644 --- a/test/system/helpers.bash +++ b/test/system/helpers.bash @@ -253,6 +253,7 @@ function is_cgroupsv1() { ! is_cgroupsv2 } +# True if cgroups v2 are enabled function is_cgroupsv2() { cgroup_type=$(stat -f -c %T /sys/fs/cgroup) test "$cgroup_type" = "cgroup2fs" @@ -305,6 +306,15 @@ function skip_if_no_selinux() { fi } +####################### +# skip_if_cgroupsv1 # ...with an optional message +####################### +function skip_if_cgroupsv1() { + if ! is_cgroupsv2; then + skip "${1:-test requires cgroupsv2}" + fi +} + ######### # die # Abort with helpful message ######### |