diff options
32 files changed, 292 insertions, 113 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index f4a25be88..656d23d67 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -5,6 +5,13 @@ BUG REPORT INFORMATION Use the commands below to provide key information from your environment: You do NOT have to include this information if this is a FEATURE REQUEST +**NOTE** A large number of issues reported against Podman are often found to already be fixed +in more current versions of the project. Before reporting an issue, please verify the +version you are running with `podman version` and compare it to the latest release +documented on the top of Podman's [README.md](../README.md). If they differ, please +update your version of Podman to the latest possible and retry your command before creating +an issue. + If you are filing a bug against `podman build`, please instead file a bug against Buildah (https://github.com/projectatomic/buildah/issues). Podman build executes Buildah to perform container builds, and as such the Buildah @@ -13,11 +20,11 @@ maintainers are best equipped to handle these bugs. **Is this a BUG REPORT or FEATURE REQUEST?**: -> Uncomment only one, leave it on its own line: -> -> /kind bug -> -> /kind feature +[//]: # Uncomment only one, leave it on its own line: + +[//]: # **kind bug** + +[//]: # **kind feature** **Description** @@ -1,6 +1,6 @@ GO ?= go DESTDIR ?= / -EPOCH_TEST_COMMIT ?= 6ffce631db6e01f66b09cb0e894600182caa872c +EPOCH_TEST_COMMIT ?= 0cb0849c722f3e1c41d2005488cc2951b8d16f53 HEAD ?= HEAD CHANGELOG_BASE ?= HEAD~ CHANGELOG_TARGET ?= HEAD @@ -286,7 +286,7 @@ install.tools: .install.gitvalidation .install.gometalinter .install.md2man .ins fi .install.easyjson: .gopathok - if [ ! -x "$(GOBIN)/ffjson" ]; then\ + if [ ! -x "$(GOBIN)/easyffjson" ]; then\ $(GO) get -u github.com/mailru/easyjson/...; \ fi @@ -1,6 +1,7 @@ ![PODMAN logo](logo/podman-logo-source.svg) # libpod - library for running OCI-based containers in Pods +### Latest Version: 0.9.1 ### Status: Active Development ## What is the scope of this project? @@ -63,12 +64,44 @@ Release notes for recent Podman versions **[Contributing](CONTRIBUTING.md)** Information about contributing to this project. -### Current Roadmap +## Current Roadmap -1. Varlink API for Podman +1. Python frontend for Varlink API 1. Integrate libpod into CRI-O to replace its existing container management backend -1. Pod commands for Podman -1. Rootless containers -1. Support for cleaning up containers via post-run hooks - -[spec-hooks]: https://github.com/opencontainers/runtime-spec/blob/v1.0.1/config.md#posix-platform-hooks +1. Further work on the podman pod command +1. Further improvements on rootless containers +1. In-memory locking to replace file locks + +[spec-hooks]: https://github.com/opencontainers/runtime-spec/blob/v2.0.1/config.md#posix-platform-hooks + +## Buildah and Podman relationship + +Buildah and Podman are two complementary Open-source projects that are available on +most Linux platforms and both projects reside at [GitHub.com](https://github.com) +with Buildah [here](https://github.com/projectatomic/buildah) and +Podman [here](https://github.com/containers/libpod). Both Buildah and Podman are +command line tools that work on OCI images and containers. The two projects +differentiate in their specialization. + +Buildah specializes in building OCI images. Buildah's commands replicate all +of the commands that are found in a Dockerfile. Buildah’s goal is also to +provide a lower level coreutils interface to build images, allowing people to build +containers without requiring a Dockerfile. The intent with Buildah is to allow other +scripting languages to build container images, without requiring a daemon. + +Podman specializes in all of the commands and functions that help you to maintain and modify +OCI images, such as pulling and tagging. It also allows you to create, run, and maintain those containers +created from those images. + +A major difference between Podman and Buildah is their concept of a container. Podman +allows users to create "traditional containers" where the intent of these containers is +to be long lived. While Buildah containers are really just created to allow content +to be added back to the container image. An easy way to think of it is the +`buildah run` command emulates the RUN command in a Dockerfile while the `podman run` +command emulates the `docker run` command in functionality. Because of this and their underlying +storage differences, you can not see Podman containers from within Buildah or vice versa. + +In short Buildah is an efficient way to create OCI images while Podman allows +you to manage and maintain those images and containers in a production environment using +familiar container cli commands. For more details, see the +[Container Tools Guide](https://github.com/projectatomic/buildah/tree/master/docs/containertools). diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 8d470e10d..a532bc26e 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -138,6 +138,17 @@ func main() { logrus.SetLevel(level) } + // Only if not rootless, set rlimits for open files. + // We open numerous FDs for ports opened + if !rootless.IsRootless() { + rlimits := new(syscall.Rlimit) + rlimits.Cur = 1048576 + rlimits.Max = 1048576 + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + return errors.Wrapf(err, "error setting new rlimits") + } + } + if logLevel == "debug" { debug = true diff --git a/cmd/podman/run.go b/cmd/podman/run.go index 42aa81cd5..2a031de05 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -117,7 +117,7 @@ func runCmd(c *cli.Context) error { return err } - if ecode, err := ctr.Wait(); err != nil { + if ecode, err := ctr.Wait(libpod.WaitTimeout); err != nil { if errors.Cause(err) == libpod.ErrNoSuchCtr { // The container may have been removed // Go looking for an exit file diff --git a/cmd/podman/search.go b/cmd/podman/search.go index 009ff8ba9..f64b822fc 100644 --- a/cmd/podman/search.go +++ b/cmd/podman/search.go @@ -345,6 +345,11 @@ func matchesOfficialFilter(filter searchFilterParams, result docker.SearchResult } func getRegistry(image string) (string, error) { + // It is possible to only have the registry name in the format "myregistry/" + // if so, just trim the "/" from the end and return the registry name + if strings.HasSuffix(image, "/") { + return strings.TrimSuffix(image, "/"), nil + } imgRef, err := reference.Parse(image) if err != nil { return "", err diff --git a/cmd/podman/start.go b/cmd/podman/start.go index cb65ec6d4..a80d0e1e8 100644 --- a/cmd/podman/start.go +++ b/cmd/podman/start.go @@ -115,7 +115,7 @@ func startCmd(c *cli.Context) error { return errors.Wrapf(err, "unable to start container %s", ctr.ID()) } - if ecode, err := ctr.Wait(); err != nil { + if ecode, err := ctr.Wait(libpod.WaitTimeout); err != nil { logrus.Errorf("unable to get exit code of container %s: %q", ctr.ID(), err) } else { exitCode = int(ecode) diff --git a/cmd/podman/wait.go b/cmd/podman/wait.go index e919ab3ca..48d3885e7 100644 --- a/cmd/podman/wait.go +++ b/cmd/podman/wait.go @@ -3,8 +3,10 @@ package main import ( "fmt" "os" + "time" "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -15,7 +17,14 @@ var ( Block until one or more containers stop and then print their exit codes ` - waitFlags = []cli.Flag{LatestFlag} + waitFlags = []cli.Flag{ + cli.UintFlag{ + Name: "interval, i", + Usage: "Milliseconds to wait before polling for completion", + Value: uint(libpod.WaitTimeout), + }, + LatestFlag, + } waitCommand = cli.Command{ Name: "wait", Usage: "Block on one or more containers", @@ -57,7 +66,10 @@ func waitCmd(c *cli.Context) error { if err != nil { return errors.Wrapf(err, "unable to find container %s", container) } - returnCode, err := ctr.Wait() + if c.Uint("interval") == 0 { + return errors.Errorf("interval must be greater then 0") + } + returnCode, err := ctr.Wait(time.Duration(c.Uint("interval"))) if err != nil { if lastError != nil { fmt.Fprintln(os.Stderr, lastError) diff --git a/completions/bash/podman b/completions/bash/podman index d9af43d37..de535512f 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2012,7 +2012,9 @@ _podman_wait() { local boolean_options=" --help -h - -l + -i + -l + --interval --latest" case "$cur" in -*) diff --git a/docs/podman-search.1.md b/docs/podman-search.1.md index 9cc9e1e6b..429c3c5ad 100644 --- a/docs/podman-search.1.md +++ b/docs/podman-search.1.md @@ -10,10 +10,12 @@ podman\-search - Search a registry for an image **podman search** searches a registry or a list of registries for a matching image. The user can specify which registry to search by prefixing the registry in the search term (example **registry.fedoraproject.org/fedora**), default is the registries in the -**registires.search** table in the config file - **/etc/containers/registries.conf**. +**registries.search** table in the config file - **/etc/containers/registries.conf**. The number of results can be limited using the **--limit** flag. If more than one registry is being searched, the limit will be applied to each registry. The output can be filtered -using the **--filter** flag. +using the **--filter** flag. To get all available images in a registry without a specific +search term, the user can just enter the registry name with a trailing "/" (example **registry.fedoraproject.org/**). +Note, searching without a search term will only work for registries that implement the v2 API. **podman [GLOBAL OPTIONS]** @@ -116,6 +118,27 @@ INDEX NAME fedoraproject.org fedoraproject.org/fedora fedoraproject.org fedoraproject.org/fedora-minimal ``` + +``` +$ podman search registry.fedoraproject.org/ +INDEX NAME DESCRIPTION STARS OFFICIAL AUTOMATED +fedoraproject.org registry.fedoraproject.org/f25/cockpit 0 +fedoraproject.org registry.fedoraproject.org/f25/container-engine 0 +fedoraproject.org registry.fedoraproject.org/f25/docker 0 +fedoraproject.org registry.fedoraproject.org/f25/etcd 0 +fedoraproject.org registry.fedoraproject.org/f25/flannel 0 +fedoraproject.org registry.fedoraproject.org/f25/httpd 0 +fedoraproject.org registry.fedoraproject.org/f25/kubernetes-apiserver 0 +fedoraproject.org registry.fedoraproject.org/f25/kubernetes-controller-manager 0 +fedoraproject.org registry.fedoraproject.org/f25/kubernetes-kubelet 0 +fedoraproject.org registry.fedoraproject.org/f25/kubernetes-master 0 +fedoraproject.org registry.fedoraproject.org/f25/kubernetes-node 0 +fedoraproject.org registry.fedoraproject.org/f25/kubernetes-proxy 0 +fedoraproject.org registry.fedoraproject.org/f25/kubernetes-scheduler 0 +fedoraproject.org registry.fedoraproject.org/f25/mariadb 0 +``` +Note: This works only with registries that implement the v2 API. If tried with a v1 registry an error will be returned. + ## FILES **registries.conf** (`/etc/containers/registries.conf`) diff --git a/docs/podman-wait.1.md b/docs/podman-wait.1.md index 74ccdbe0c..dd5dc7907 100644 --- a/docs/podman-wait.1.md +++ b/docs/podman-wait.1.md @@ -17,6 +17,9 @@ After the container stops, the container's return code is printed. Print usage statement +**--interval, i**" + Microseconds to wait before polling for completion + **--latest, -l** Instead of providing the container name or ID, use the last created container. If you use methods other than Podman diff --git a/libpod.conf b/libpod.conf index cc4a10cff..dcfeb67cc 100644 --- a/libpod.conf +++ b/libpod.conf @@ -80,3 +80,11 @@ pause_image = "k8s.gcr.io/pause:3.1" # Default command to run the pause container pause_command = "/pause" + +# Determines whether libpod will reserve ports on the host when they are +# forwarded to containers. When enabled, when ports are forwarded to containers, +# they are held open by conmon as long as the container is running, ensuring that +# they cannot be reused by other programs on the host. However, this can cause +# significant memory usage if a container has many ports forwarded to it. +# Disabling this can save memory. +#enable_port_reservation = true diff --git a/libpod/container.go b/libpod/container.go index e748cb84d..f68a3535e 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -36,6 +36,8 @@ const ( ContainerStateStopped ContainerStatus = iota // ContainerStatePaused indicates that the container has been paused ContainerStatePaused ContainerStatus = iota + // WaitTimeout is the wait timeout before checking for container exit + WaitTimeout = time.Second / time.Millisecond ) // CgroupfsDefaultCgroupParent is the cgroup parent for CGroupFS in libpod diff --git a/libpod/container_api.go b/libpod/container_api.go index 86e2370ea..437699bae 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -592,12 +592,11 @@ func (c *Container) Inspect(size bool) (*inspect.ContainerInspectData, error) { } // Wait blocks on a container to exit and returns its exit code -func (c *Container) Wait() (int32, error) { +func (c *Container) Wait(waitTimeout time.Duration) (int32, error) { if !c.valid { return -1, ErrCtrRemoved } - - err := wait.PollImmediateInfinite(100*time.Millisecond, + err := wait.PollImmediateInfinite(waitTimeout*time.Millisecond, func() (bool, error) { stopped, err := c.isStopped() if err != nil { diff --git a/libpod/image/pull.go b/libpod/image/pull.go index ce3e8e73e..9eac2b988 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -20,6 +20,7 @@ import ( "github.com/containers/image/types" "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/util" + multierror "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -234,6 +235,7 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa return nil, err } var images []string + var pullErrors *multierror.Error for _, imageInfo := range goal.refPairs { copyOptions := getCopyOptions(sc, writer, dockerOptions, nil, signingOptions, "", nil) if imageInfo.srcRef.Transport().Name() == DockerTransport { @@ -254,6 +256,7 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa io.WriteString(writer, fmt.Sprintf("Trying to pull %s...", imageInfo.image)) } if err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions); err != nil { + pullErrors = multierror.Append(pullErrors, err) logrus.Debugf("Error pulling image ref %s: %v", imageInfo.srcRef.StringWithinTransport(), err) if writer != nil { io.WriteString(writer, "Failed\n") @@ -273,10 +276,12 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa } // If the image passed in was fully-qualified, we will have 1 refpair. Bc the image is fq'd, we dont need to yap about registries. if !goal.usedSearchRegistries { + if pullErrors != nil && len(pullErrors.Errors) > 0 { // this should always be true + return nil, errors.Wrap(pullErrors.Errors[0], "unable to pull image") + } return nil, errors.Errorf("unable to pull image, or you do not have pull access") } - return nil, errors.Errorf("unable to find image on registries defined in %s, or you do not have pull access", registryPath) - + return nil, pullErrors } return images, nil } diff --git a/libpod/oci.go b/libpod/oci.go index e1c0d1261..3838394cb 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -66,6 +66,7 @@ type OCIRuntime struct { socketsDir string logSizeMax int64 noPivot bool + reservePorts bool } // syncInfo is used to return data from monitor process to daemon @@ -75,7 +76,7 @@ type syncInfo struct { } // Make a new OCI runtime with provided options -func newOCIRuntime(name string, path string, conmonPath string, conmonEnv []string, cgroupManager string, tmpDir string, logSizeMax int64, noPivotRoot bool) (*OCIRuntime, error) { +func newOCIRuntime(name string, path string, conmonPath string, conmonEnv []string, cgroupManager string, tmpDir string, logSizeMax int64, noPivotRoot bool, reservePorts bool) (*OCIRuntime, error) { runtime := new(OCIRuntime) runtime.name = name runtime.path = path @@ -85,6 +86,7 @@ func newOCIRuntime(name string, path string, conmonPath string, conmonEnv []stri runtime.tmpDir = tmpDir runtime.logSizeMax = logSizeMax runtime.noPivot = noPivotRoot + runtime.reservePorts = reservePorts runtime.exitsDir = filepath.Join(runtime.tmpDir, "exits") runtime.socketsDir = filepath.Join(runtime.tmpDir, "socket") @@ -311,15 +313,17 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string) (er cmd.Env = append(cmd.Env, fmt.Sprintf("_OCI_STARTPIPE=%d", 4)) cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) - ports, err := bindPorts(ctr.config.PortMappings) - if err != nil { - return err - } + if r.reservePorts { + ports, err := bindPorts(ctr.config.PortMappings) + if err != nil { + return err + } - // Leak the port we bound in the conmon process. These fd's won't be used - // by the container and conmon will keep the ports busy so that another - // process cannot use them. - cmd.ExtraFiles = append(cmd.ExtraFiles, ports...) + // Leak the port we bound in the conmon process. These fd's won't be used + // by the container and conmon will keep the ports busy so that another + // process cannot use them. + cmd.ExtraFiles = append(cmd.ExtraFiles, ports...) + } if rootless.IsRootless() { ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() diff --git a/libpod/runtime.go b/libpod/runtime.go index 63b8c971e..736169932 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -164,6 +164,14 @@ type RuntimeConfig struct { InfraImage string `toml:"infra_image"` // InfraCommand is the command run to start up a pod infra container InfraCommand string `toml:"infra_command"` + // EnablePortReservation determines whether libpod will reserve ports on + // the host when they are forwarded to containers. + // When enabled, when ports are forwarded to containers, they are + // held open by conmon as long as the container is running, ensuring + // that they cannot be reused by other programs on the host. + // However, this can cause significant memory usage if a container has + // many ports forwarded to it. Disabling this can save memory. + EnablePortReservation bool `toml:"enable_port_reservation"` } var ( @@ -190,16 +198,17 @@ var ( ConmonEnvVars: []string{ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", }, - CgroupManager: SystemdCgroupsManager, - HooksDir: hooks.DefaultDir, - StaticDir: filepath.Join(storage.DefaultStoreOptions.GraphRoot, "libpod"), - TmpDir: "", - MaxLogSize: -1, - NoPivotRoot: false, - CNIConfigDir: "/etc/cni/net.d/", - CNIPluginDir: []string{"/usr/libexec/cni", "/usr/lib/cni", "/opt/cni/bin"}, - InfraCommand: DefaultInfraCommand, - InfraImage: DefaultInfraImage, + CgroupManager: SystemdCgroupsManager, + HooksDir: hooks.DefaultDir, + StaticDir: filepath.Join(storage.DefaultStoreOptions.GraphRoot, "libpod"), + TmpDir: "", + MaxLogSize: -1, + NoPivotRoot: false, + CNIConfigDir: "/etc/cni/net.d/", + CNIPluginDir: []string{"/usr/libexec/cni", "/usr/lib/cni", "/opt/cni/bin"}, + InfraCommand: DefaultInfraCommand, + InfraImage: DefaultInfraImage, + EnablePortReservation: true, } ) @@ -467,7 +476,8 @@ func makeRuntime(runtime *Runtime) (err error) { ociRuntime, err := newOCIRuntime("runc", runtime.ociRuntimePath, runtime.conmonPath, runtime.config.ConmonEnvVars, runtime.config.CgroupManager, runtime.config.TmpDir, - runtime.config.MaxLogSize, runtime.config.NoPivotRoot) + runtime.config.MaxLogSize, runtime.config.NoPivotRoot, + runtime.config.EnablePortReservation) if err != nil { return err } diff --git a/pkg/secrets/secrets.go b/pkg/secrets/secrets.go index be5642eba..7208f53b7 100644 --- a/pkg/secrets/secrets.go +++ b/pkg/secrets/secrets.go @@ -243,7 +243,7 @@ func addSecretsFromMountsFile(filePath, mountLabel, containerWorkingDir, mountPr Source: filepath.Join(mountPrefix, ctrDir), Destination: ctrDir, Type: "bind", - Options: []string{"bind", "private"}, + Options: []string{"bind", "rprivate"}, } mounts = append(mounts, m) @@ -278,7 +278,7 @@ func addFIPSModeSecret(mounts *[]rspec.Mount, containerWorkingDir string) error Source: ctrDirOnHost, Destination: secretsDir, Type: "bind", - Options: []string{"bind", "private"}, + Options: []string{"bind", "rprivate"}, } *mounts = append(*mounts, m) } diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 3cca345b4..a441b4019 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -189,7 +189,7 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e } } if rootProp == "" { - options = append(options, "private") + options = append(options, "rprivate") } m = append(m, spec.Mount{ @@ -214,7 +214,7 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e Destination: vol, Type: string(TypeTmpfs), Source: string(TypeTmpfs), - Options: []string{"private", "rw", "noexec", "nosuid", "nodev", "tmpcopyup"}, + Options: []string{"rprivate", "rw", "noexec", "nosuid", "nodev", "tmpcopyup"}, } m = append(m, mount) } @@ -272,7 +272,7 @@ func (c *CreateConfig) GetTmpfsMounts() []spec.Mount { var m []spec.Mount for _, i := range c.Tmpfs { // Default options if nothing passed - options := []string{"private", "rw", "noexec", "nosuid", "nodev", "size=65536k"} + options := []string{"rprivate", "rw", "noexec", "nosuid", "nodev", "size=65536k"} spliti := strings.Split(i, ":") destPath := spliti[0] if len(spliti) > 1 { diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 77dbf8b42..cc3501e1e 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -43,7 +43,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint Destination: "/sys", Type: "sysfs", Source: "sysfs", - Options: []string{"private", "nosuid", "noexec", "nodev", "rw"}, + Options: []string{"rprivate", "nosuid", "noexec", "nodev", "rw"}, } g.AddMount(sysMnt) } else if !canMountSys { @@ -57,7 +57,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint Destination: "/sys", Type: "bind", Source: "/sys", - Options: []string{"nosuid", "noexec", "nodev", r, "rbind"}, + Options: []string{"rprivate", "nosuid", "noexec", "nodev", r, "rbind"}, } g.AddMount(sysMnt) } @@ -67,7 +67,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint Destination: "/dev/pts", Type: "devpts", Source: "devpts", - Options: []string{"private", "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"}, + Options: []string{"rprivate", "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"}, } g.AddMount(devPts) } @@ -97,7 +97,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint Destination: "/sys/fs/cgroup", Type: "cgroup", Source: "cgroup", - Options: []string{"private", "nosuid", "noexec", "nodev", "relatime", cgroupPerm}, + Options: []string{"rprivate", "nosuid", "noexec", "nodev", "relatime", cgroupPerm}, } g.AddMount(cgroupMnt) } @@ -231,7 +231,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint } for _, i := range config.Tmpfs { // Default options if nothing passed - options := []string{"rw", "private", "noexec", "nosuid", "nodev", "size=65536k"} + options := []string{"rw", "rprivate", "noexec", "nosuid", "nodev", "size=65536k"} spliti := strings.SplitN(i, ":", 2) if len(spliti) > 1 { if _, _, err := mount.ParseTmpfsOptions(spliti[1]); err != nil { @@ -385,7 +385,7 @@ func setupSystemd(config *CreateConfig, g *generate.Generator) error { if err != nil { return err } - options := []string{"rw", "private", "noexec", "nosuid", "nodev"} + options := []string{"rw", "rprivate", "noexec", "nosuid", "nodev"} for _, dest := range []string{"/run", "/run/lock", "/sys/fs/cgroup/systemd"} { if libpod.MountExists(mounts, dest) { continue diff --git a/pkg/spec/spec_test.go b/pkg/spec/spec_test.go index de3605068..c037bf69e 100644 --- a/pkg/spec/spec_test.go +++ b/pkg/spec/spec_test.go @@ -13,7 +13,7 @@ func TestCreateConfig_GetVolumeMounts(t *testing.T) { Destination: "/foobar", Type: "bind", Source: "foobar", - Options: []string{"ro", "rbind", "private"}, + Options: []string{"ro", "rbind", "rprivate"}, } config := CreateConfig{ Volumes: []string{"foobar:/foobar:ro"}, diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index f517e9b6e..de9c23034 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -341,7 +341,7 @@ func (i *LibpodAPI) WaitContainer(call iopodman.VarlinkCall, name string) error if err != nil { return call.ReplyContainerNotFound(name) } - exitCode, err := ctr.Wait() + exitCode, err := ctr.Wait(libpod.WaitTimeout) if err != nil { return call.ReplyErrorOccurred(err.Error()) } diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index a90b72a6d..54bce3d35 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -34,6 +34,9 @@ func (i *LibpodAPI) Ping(call iopodman.VarlinkCall) error { // GetInfo returns details about the podman host and its stores func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { + var ( + registries, insecureRegistries []string + ) podmanInfo := iopodman.PodmanInfo{} info, err := i.Runtime.Info() if err != nil { @@ -76,7 +79,19 @@ func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { Graph_status: graphStatus, } + registriesInterface := info[2].Data["registries"] + insecureRegistriesInterface := info[3].Data["registries"] + if registriesInterface != nil { + registries = registriesInterface.([]string) + } + if insecureRegistriesInterface != nil { + insecureRegistries = insecureRegistriesInterface.([]string) + } + podmanInfo.Store = infoStore podmanInfo.Podman = pmaninfo + podmanInfo.Registries = registries + podmanInfo.Insecure_registries = insecureRegistries + return call.ReplyGetInfo(podmanInfo) } diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index 7b9612a35..1f06bf4a1 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -60,10 +60,10 @@ var _ = Describe("Podman search", func() { }) It("podman search single registry flag", func() { - search := podmanTest.Podman([]string{"search", "registry.fedoraproject.org/fedora-minimal"}) + search := podmanTest.Podman([]string{"search", "registry.fedoraproject.org/fedora"}) search.WaitWithDefaultTimeout() Expect(search.ExitCode()).To(Equal(0)) - Expect(search.LineInOutputContains("fedoraproject.org/fedora-minimal")).To(BeTrue()) + Expect(search.LineInOutputContains("fedoraproject.org/fedora")).To(BeTrue()) }) It("podman search format flag", func() { @@ -84,7 +84,7 @@ var _ = Describe("Podman search", func() { }) It("podman search limit flag", func() { - search := podmanTest.Podman([]string{"search", "--limit", "3", "alpine"}) + search := podmanTest.Podman([]string{"search", "--limit", "3", "docker.io/alpine"}) search.WaitWithDefaultTimeout() Expect(search.ExitCode()).To(Equal(0)) Expect(len(search.OutputToStringArray())).To(Equal(4)) @@ -120,6 +120,13 @@ var _ = Describe("Podman search", func() { } }) + It("podman search v2 registry with empty query", func() { + search := podmanTest.Podman([]string{"search", "registry.fedoraproject.org/"}) + search.WaitWithDefaultTimeout() + Expect(search.ExitCode()).To(Equal(0)) + Expect(len(search.OutputToStringArray())).To(BeNumerically(">=", 1)) + }) + It("podman search attempts HTTP if tls-verify flag is set false", func() { podmanTest.RestoreArtifact(registry) fakereg := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", "5000:5000", registry, "/entrypoint.sh", "/etc/docker/registry/config.yml"}) diff --git a/troubleshooting.md b/troubleshooting.md index d0d84f7cc..db36d1bb8 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -5,7 +5,18 @@ ## A list of common issues and solutions for Podman --- -### 1) No such image or Bare keys cannot contain ':' +### 1) Variety of issues - Validate Version + +A large number of issues reported against Podman are often found to already be fixed +in more current versions of the project. Before reporting an issue, please verify the +version you are running with `podman version` and compare it to the lastest release +documented on the top of Podman's [README.md](README.md). + +If they differ, please update your version of PODMAN to the latest possible +and retry your command before reporting the issue. + +--- +### 2) No such image or Bare keys cannot contain ':' When doing a `podman pull` or `podman build` command and a "common" image can not be pulled, it is likely that the `/etc/containers/registries.conf` file is either not installed or possibly @@ -33,7 +44,7 @@ error pulling image "fedora": unable to pull fedora: error getting default regis * i.e. `registries = ['registry.fedoraproject.org', 'quay.io', 'registry.access.redhat.com']` --- -### 2) http: server gave HTTP response to HTTPS client +### 3) http: server gave HTTP response to HTTPS client When doing a Podman command such as `build`, `commit`, `pull`, or `push` to a registry, tls verification is turned on by default. If authentication is not used with diff --git a/vendor.conf b/vendor.conf index 390615dfa..c21cb4b8e 100644 --- a/vendor.conf +++ b/vendor.conf @@ -10,7 +10,7 @@ github.com/containerd/cgroups 58556f5ad8448d99a6f7bea69ea4bdb7747cfeb0 github.com/containerd/continuity master github.com/containernetworking/cni v0.7.0-alpha1 github.com/containernetworking/plugins 1562a1e60ed101aacc5e08ed9dbeba8e9f3d4ec1 -github.com/containers/image 5df44e095ed826fbe2beeaabb329c749d7d6c3b6 +github.com/containers/image d8b5cf2b804a48489e5203d51254ef576794049d github.com/containers/storage 243c4cd616afdf06b4a975f18c4db083d26b1641 github.com/containers/psgo 5dde6da0bc8831b35243a847625bcf18183bd1ee github.com/coreos/go-systemd v14 diff --git a/vendor/github.com/containers/image/docker/docker_client.go b/vendor/github.com/containers/image/docker/docker_client.go index cae73a6cd..4fb10c395 100644 --- a/vendor/github.com/containers/image/docker/docker_client.go +++ b/vendor/github.com/containers/image/docker/docker_client.go @@ -100,6 +100,19 @@ type authScope struct { actions string } +// sendAuth determines whether we need authentication for v2 or v1 endpoint. +type sendAuth int + +const ( + // v2 endpoint with authentication. + v2Auth sendAuth = iota + // v1 endpoint with authentication. + // TODO: Get v1Auth working + // v1Auth + // no authentication, works for both v1 and v2. + noAuth +) + func newBearerTokenFromJSONBlob(blob []byte) (*bearerToken, error) { token := new(bearerToken) if err := json.Unmarshal(blob, &token); err != nil { @@ -234,7 +247,7 @@ func CheckAuth(ctx context.Context, sys *types.SystemContext, username, password return errors.Wrapf(err, "error creating new docker client") } - resp, err := newLoginClient.makeRequest(ctx, "GET", "/v2/", nil, nil) + resp, err := newLoginClient.makeRequest(ctx, "GET", "/v2/", nil, nil, v2Auth) if err != nil { return err } @@ -297,14 +310,43 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima return nil, errors.Wrapf(err, "error creating new docker client") } + // Only try the v1 search endpoint if the search query is not empty. If it is + // empty skip to the v2 endpoint. + if image != "" { + // set up the query values for the v1 endpoint + u := url.URL{ + Path: "/v1/search", + } + q := u.Query() + q.Set("q", image) + q.Set("n", strconv.Itoa(limit)) + u.RawQuery = q.Encode() + + logrus.Debugf("trying to talk to v1 search endpoint\n") + resp, err := client.makeRequest(ctx, "GET", u.String(), nil, nil, noAuth) + if err != nil { + logrus.Debugf("error getting search results from v1 endpoint %q: %v", registry, err) + } else { + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + logrus.Debugf("error getting search results from v1 endpoint %q, status code %d", registry, resp.StatusCode) + } else { + if err := json.NewDecoder(resp.Body).Decode(v1Res); err != nil { + return nil, err + } + return v1Res.Results, nil + } + } + } + logrus.Debugf("trying to talk to v2 search endpoint\n") - resp, err := client.makeRequest(ctx, "GET", "/v2/_catalog", nil, nil) + resp, err := client.makeRequest(ctx, "GET", "/v2/_catalog", nil, nil, v2Auth) if err != nil { logrus.Debugf("error getting search results from v2 endpoint %q: %v", registry, err) } else { defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - logrus.Debugf("error getting search results from v2 endpoint %q, status code %q", registry, resp.StatusCode) + logrus.Errorf("error getting search results from v2 endpoint %q, status code %d", registry, resp.StatusCode) } else { if err := json.NewDecoder(resp.Body).Decode(v2Res); err != nil { return nil, err @@ -322,50 +364,25 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima } } - // set up the query values for the v1 endpoint - u := url.URL{ - Path: "/v1/search", - } - q := u.Query() - q.Set("q", image) - q.Set("n", strconv.Itoa(limit)) - u.RawQuery = q.Encode() - - logrus.Debugf("trying to talk to v1 search endpoint\n") - resp, err = client.makeRequest(ctx, "GET", u.String(), nil, nil) - if err != nil { - logrus.Debugf("error getting search results from v1 endpoint %q: %v", registry, err) - } else { - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - logrus.Debugf("error getting search results from v1 endpoint %q, status code %q", registry, resp.StatusCode) - } else { - if err := json.NewDecoder(resp.Body).Decode(v1Res); err != nil { - return nil, err - } - return v1Res.Results, nil - } - } - return nil, errors.Wrapf(err, "couldn't search registry %q", registry) } // makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client. // The host name and schema is taken from the client or autodetected, and the path is relative to it, i.e. the path usually starts with /v2/. -func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader) (*http.Response, error) { +func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader, auth sendAuth) (*http.Response, error) { if err := c.detectProperties(ctx); err != nil { return nil, err } url := fmt.Sprintf("%s://%s%s", c.scheme, c.registry, path) - return c.makeRequestToResolvedURL(ctx, method, url, headers, stream, -1, true) + return c.makeRequestToResolvedURL(ctx, method, url, headers, stream, -1, auth) } // makeRequestToResolvedURL creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client. // streamLen, if not -1, specifies the length of the data expected on stream. // makeRequest should generally be preferred. // TODO(runcom): too many arguments here, use a struct -func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url string, headers map[string][]string, stream io.Reader, streamLen int64, sendAuth bool) (*http.Response, error) { +func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url string, headers map[string][]string, stream io.Reader, streamLen int64, auth sendAuth) (*http.Response, error) { req, err := http.NewRequest(method, url, stream) if err != nil { return nil, err @@ -383,7 +400,7 @@ func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url if c.sys != nil && c.sys.DockerRegistryUserAgent != "" { req.Header.Add("User-Agent", c.sys.DockerRegistryUserAgent) } - if sendAuth { + if auth == v2Auth { if err := c.setupRequestAuth(req); err != nil { return nil, err } @@ -497,7 +514,7 @@ func (c *dockerClient) detectProperties(ctx context.Context) error { ping := func(scheme string) error { url := fmt.Sprintf(resolvedPingV2URL, scheme, c.registry) - resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, true) + resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth) if err != nil { logrus.Debugf("Ping %s err %#v", url, err) return err @@ -524,7 +541,7 @@ func (c *dockerClient) detectProperties(ctx context.Context) error { // best effort to understand if we're talking to a V1 registry pingV1 := func(scheme string) bool { url := fmt.Sprintf(resolvedPingV1URL, scheme, c.registry) - resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, true) + resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth) logrus.Debugf("Ping %s err %#v", url, err) if err != nil { return false @@ -551,7 +568,7 @@ func (c *dockerClient) detectProperties(ctx context.Context) error { // using the original data structures. func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerReference, manifestDigest digest.Digest) (*extensionSignatureList, error) { path := fmt.Sprintf(extensionsSignaturePath, reference.Path(ref.ref), manifestDigest) - res, err := c.makeRequest(ctx, "GET", path, nil, nil) + res, err := c.makeRequest(ctx, "GET", path, nil, nil, v2Auth) if err != nil { return nil, err } diff --git a/vendor/github.com/containers/image/docker/docker_image.go b/vendor/github.com/containers/image/docker/docker_image.go index 010791241..a1a115080 100644 --- a/vendor/github.com/containers/image/docker/docker_image.go +++ b/vendor/github.com/containers/image/docker/docker_image.go @@ -66,7 +66,7 @@ func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types. tags := make([]string, 0) for { - res, err := client.makeRequest(ctx, "GET", path, nil, nil) + res, err := client.makeRequest(ctx, "GET", path, nil, nil, v2Auth) if err != nil { return nil, err } diff --git a/vendor/github.com/containers/image/docker/docker_image_dest.go b/vendor/github.com/containers/image/docker/docker_image_dest.go index 85d568995..94763d026 100644 --- a/vendor/github.com/containers/image/docker/docker_image_dest.go +++ b/vendor/github.com/containers/image/docker/docker_image_dest.go @@ -130,7 +130,7 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader, // FIXME? Chunked upload, progress reporting, etc. uploadPath := fmt.Sprintf(blobUploadPath, reference.Path(d.ref.ref)) logrus.Debugf("Uploading %s", uploadPath) - res, err := d.c.makeRequest(ctx, "POST", uploadPath, nil, nil) + res, err := d.c.makeRequest(ctx, "POST", uploadPath, nil, nil, v2Auth) if err != nil { return types.BlobInfo{}, err } @@ -147,7 +147,7 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader, digester := digest.Canonical.Digester() sizeCounter := &sizeCounter{} tee := io.TeeReader(stream, io.MultiWriter(digester.Hash(), sizeCounter)) - res, err = d.c.makeRequestToResolvedURL(ctx, "PATCH", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, tee, inputInfo.Size, true) + res, err = d.c.makeRequestToResolvedURL(ctx, "PATCH", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, tee, inputInfo.Size, v2Auth) if err != nil { logrus.Debugf("Error uploading layer chunked, response %#v", res) return types.BlobInfo{}, err @@ -166,7 +166,7 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader, // TODO: check inputInfo.Digest == computedDigest https://github.com/containers/image/pull/70#discussion_r77646717 locationQuery.Set("digest", computedDigest.String()) uploadLocation.RawQuery = locationQuery.Encode() - res, err = d.c.makeRequestToResolvedURL(ctx, "PUT", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, nil, -1, true) + res, err = d.c.makeRequestToResolvedURL(ctx, "PUT", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, nil, -1, v2Auth) if err != nil { return types.BlobInfo{}, err } @@ -191,7 +191,7 @@ func (d *dockerImageDestination) HasBlob(ctx context.Context, info types.BlobInf checkPath := fmt.Sprintf(blobsPath, reference.Path(d.ref.ref), info.Digest.String()) logrus.Debugf("Checking %s", checkPath) - res, err := d.c.makeRequest(ctx, "HEAD", checkPath, nil, nil) + res, err := d.c.makeRequest(ctx, "HEAD", checkPath, nil, nil, v2Auth) if err != nil { return false, -1, err } @@ -237,7 +237,7 @@ func (d *dockerImageDestination) PutManifest(ctx context.Context, m []byte) erro if mimeType != "" { headers["Content-Type"] = []string{mimeType} } - res, err := d.c.makeRequest(ctx, "PUT", path, headers, bytes.NewReader(m)) + res, err := d.c.makeRequest(ctx, "PUT", path, headers, bytes.NewReader(m), v2Auth) if err != nil { return err } @@ -442,7 +442,7 @@ sigExists: } path := fmt.Sprintf(extensionsSignaturePath, reference.Path(d.ref.ref), d.manifestDigest.String()) - res, err := d.c.makeRequest(ctx, "PUT", path, nil, bytes.NewReader(body)) + res, err := d.c.makeRequest(ctx, "PUT", path, nil, bytes.NewReader(body), v2Auth) if err != nil { return err } diff --git a/vendor/github.com/containers/image/docker/docker_image_src.go b/vendor/github.com/containers/image/docker/docker_image_src.go index 46145dff6..3ff826aaa 100644 --- a/vendor/github.com/containers/image/docker/docker_image_src.go +++ b/vendor/github.com/containers/image/docker/docker_image_src.go @@ -89,7 +89,7 @@ func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest strin path := fmt.Sprintf(manifestPath, reference.Path(s.ref.ref), tagOrDigest) headers := make(map[string][]string) headers["Accept"] = manifest.DefaultRequestedManifestMIMETypes - res, err := s.c.makeRequest(ctx, "GET", path, headers, nil) + res, err := s.c.makeRequest(ctx, "GET", path, headers, nil, v2Auth) if err != nil { return nil, "", err } @@ -137,7 +137,7 @@ func (s *dockerImageSource) getExternalBlob(ctx context.Context, urls []string) err error ) for _, url := range urls { - resp, err = s.c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, false) + resp, err = s.c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth) if err == nil { if resp.StatusCode != http.StatusOK { err = errors.Errorf("error fetching external blob from %q: %d", url, resp.StatusCode) @@ -169,7 +169,7 @@ func (s *dockerImageSource) GetBlob(ctx context.Context, info types.BlobInfo) (i path := fmt.Sprintf(blobsPath, reference.Path(s.ref.ref), info.Digest.String()) logrus.Debugf("Downloading %s", path) - res, err := s.c.makeRequest(ctx, "GET", path, nil, nil) + res, err := s.c.makeRequest(ctx, "GET", path, nil, nil, v2Auth) if err != nil { return nil, 0, err } @@ -332,7 +332,7 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere return err } getPath := fmt.Sprintf(manifestPath, reference.Path(ref.ref), refTail) - get, err := c.makeRequest(ctx, "GET", getPath, headers, nil) + get, err := c.makeRequest(ctx, "GET", getPath, headers, nil, v2Auth) if err != nil { return err } @@ -354,7 +354,7 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere // When retrieving the digest from a registry >= 2.3 use the following header: // "Accept": "application/vnd.docker.distribution.manifest.v2+json" - delete, err := c.makeRequest(ctx, "DELETE", deletePath, headers, nil) + delete, err := c.makeRequest(ctx, "DELETE", deletePath, headers, nil, v2Auth) if err != nil { return err } diff --git a/vendor/github.com/containers/image/ostree/ostree_dest.go b/vendor/github.com/containers/image/ostree/ostree_dest.go index 0ae30f1f4..afff7dc1b 100644 --- a/vendor/github.com/containers/image/ostree/ostree_dest.go +++ b/vendor/github.com/containers/image/ostree/ostree_dest.go @@ -467,7 +467,9 @@ func (d *ostreeImageDestination) Commit(ctx context.Context) error { metadata := []string{fmt.Sprintf("docker.manifest=%s", string(d.manifest)), fmt.Sprintf("signatures=%d", d.signaturesLen), fmt.Sprintf("docker.digest=%s", string(d.digest))} - err = d.ostreeCommit(repo, fmt.Sprintf("ociimage/%s", d.ref.branchName), manifestPath, metadata) + if err := d.ostreeCommit(repo, fmt.Sprintf("ociimage/%s", d.ref.branchName), manifestPath, metadata); err != nil { + return err + } _, err = repo.CommitTransaction() return err diff --git a/version/version.go b/version/version.go index d7c3b683e..7943adee4 100644 --- a/version/version.go +++ b/version/version.go @@ -1,4 +1,7 @@ package version // Version is the version of the build. +// NOTE: remember to bump the version at the top +// of the top-level README.md file when this is +// bumped. const Version = "0.9.2-dev" |