diff options
42 files changed, 596 insertions, 499 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 6071a6fa7..5a4815e1c 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -254,20 +254,27 @@ bindings_task: always: *runner_stats -# Build the "libpod" API documentation `swagger.yaml` for eventual -# publishing along side the official podman documentation. +# Build the "libpod" API documentation `swagger.yaml` and +# publish it to google-cloud-storage (GCS). swagger_task: name: "Test Swagger" alias: swagger depends_on: - build - container: *smallcontainer + gce_instance: *standardvm env: <<: *stdenvars TEST_FLAVOR: swagger - TEST_ENVIRON: container - CTR_FQIN: ${FEDORA_CONTAINER_FQIN} - clone_script: *full_clone # build-cache not available to container tasks + # TODO: Due to podman 3.0 activity (including new images), avoid + # disturbing the status-quo just to incorporate this one new + # container image. Uncomment line below when CI activities normalize. + #CTR_FQIN: 'quay.io/libpod/gcsupld:${IMAGE_SUFFIX}' + CTR_FQIN: 'quay.io/libpod/gcsupld:c4813063494828032' + GCPJSON: ENCRYPTED[927dc01e755eaddb4242b0845cf86c9098d1e3dffac38c70aefb1487fd8b4fe6dd6ae627b3bffafaba70e2c63172664e] + GCPNAME: ENCRYPTED[c145e9c16b6fb88d476944a454bf4c1ccc84bb4ecaca73bdd28bdacef0dfa7959ebc8171a27b2e4064d66093b2cdba49] + GCPPROJECT: 'libpod-218412' + gopath_cache: *ro_gopath_cache + clone_script: *noop # Comes from cache setup_script: *setup main_script: *main always: *binary_artifacts diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 17fba5427..915ff63b6 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -474,29 +474,29 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { ) _ = cmd.RegisterFlagCompletionFunc(oomScoreAdjFlagName, completion.AutocompleteNone) - overrideArchFlagName := "override-arch" + archFlagName := "arch" createFlags.StringVar( - &cf.OverrideArch, - overrideArchFlagName, "", + &cf.Arch, + archFlagName, "", "use `ARCH` instead of the architecture of the machine for choosing images", ) - _ = cmd.RegisterFlagCompletionFunc(overrideArchFlagName, completion.AutocompleteNone) + _ = cmd.RegisterFlagCompletionFunc(archFlagName, completion.AutocompleteArch) - overrideOSFlagName := "override-os" + osFlagName := "os" createFlags.StringVar( - &cf.OverrideOS, - overrideOSFlagName, "", + &cf.OS, + osFlagName, "", "use `OS` instead of the running OS for choosing images", ) - _ = cmd.RegisterFlagCompletionFunc(overrideOSFlagName, completion.AutocompleteNone) + _ = cmd.RegisterFlagCompletionFunc(osFlagName, completion.AutocompleteOS) - overrideVariantFlagName := "override-variant" + variantFlagName := "variant" createFlags.StringVar( - &cf.OverrideVariant, - overrideVariantFlagName, "", + &cf.Variant, + variantFlagName, "", "Use _VARIANT_ instead of the running architecture variant for choosing images", ) - _ = cmd.RegisterFlagCompletionFunc(overrideVariantFlagName, completion.AutocompleteNone) + _ = cmd.RegisterFlagCompletionFunc(variantFlagName, completion.AutocompleteNone) pidFlagName := "pid" createFlags.String( @@ -516,7 +516,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { createFlags.StringVar( &cf.Platform, platformFlagName, "", - "Specify the platform for selecting the image. (Conflicts with override-arch and override-os)", + "Specify the platform for selecting the image. (Conflicts with --arch and --os)", ) _ = cmd.RegisterFlagCompletionFunc(platformFlagName, completion.AutocompleteNone) diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index f252618ce..a4da8da9e 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -73,9 +73,9 @@ type ContainerCLIOpts struct { NoHealthCheck bool OOMKillDisable bool OOMScoreAdj int - OverrideArch string - OverrideOS string - OverrideVariant string + Arch string + OS string + Variant string PID string PIDsLimit *int64 Platform string @@ -346,9 +346,9 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup LogOptions: stringMaptoArray(cc.HostConfig.LogConfig.Config), Name: cc.Name, OOMScoreAdj: cc.HostConfig.OomScoreAdj, - OverrideArch: "", - OverrideOS: "", - OverrideVariant: "", + Arch: "", + OS: "", + Variant: "", PID: string(cc.HostConfig.PidMode), PIDsLimit: cc.HostConfig.PidsLimit, Privileged: cc.HostConfig.Privileged, diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 420813ba9..5c6c773eb 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -237,17 +237,20 @@ func pullImage(imageName string) (string, error) { imageMissing = !br.Value } - if cliVals.Platform != "" { - if cliVals.OverrideArch != "" || cliVals.OverrideOS != "" { - return "", errors.Errorf("--platform option can not be specified with --override-arch or --override-os") - } - split := strings.SplitN(cliVals.Platform, "/", 2) - cliVals.OverrideOS = split[0] - if len(split) > 1 { - cliVals.OverrideArch = split[1] + if cliVals.Platform != "" || cliVals.Arch != "" || cliVals.OS != "" { + if cliVals.Platform != "" { + if cliVals.Arch != "" || cliVals.OS != "" { + return "", errors.Errorf("--platform option can not be specified with --arch or --os") + } + split := strings.SplitN(cliVals.Platform, "/", 2) + cliVals.OS = split[0] + if len(split) > 1 { + cliVals.Arch = split[1] + } } + if pullPolicy != config.PullImageAlways { - logrus.Info("--platform causes the pull policy to be \"always\"") + logrus.Info("--platform --arch and --os causes the pull policy to be \"always\"") pullPolicy = config.PullImageAlways } } @@ -259,9 +262,9 @@ func pullImage(imageName string) (string, error) { pullReport, pullErr := registry.ImageEngine().Pull(registry.GetContext(), imageName, entities.ImagePullOptions{ Authfile: cliVals.Authfile, Quiet: cliVals.Quiet, - OverrideArch: cliVals.OverrideArch, - OverrideOS: cliVals.OverrideOS, - OverrideVariant: cliVals.OverrideVariant, + Arch: cliVals.Arch, + OS: cliVals.OS, + Variant: cliVals.Variant, SignaturePolicy: cliVals.SignaturePolicy, PullPolicy: pullPolicy, }) diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go index 1029e03d1..4219e325b 100644 --- a/cmd/podman/images/build.go +++ b/cmd/podman/images/build.go @@ -423,10 +423,10 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil Ulimit: flags.Ulimit, Volumes: flags.Volumes, }, - Compression: compression, - ConfigureNetwork: networkPolicy, - ContextDirectory: contextDir, - // DefaultMountsFilePath: FIXME: this requires global flags to be working! + Compression: compression, + ConfigureNetwork: networkPolicy, + ContextDirectory: contextDir, + DefaultMountsFilePath: containerConfig.Containers.DefaultMountsFile, Devices: flags.Devices, DropCapabilities: flags.CapDrop, Err: stderr, diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index 2d881a906..fe92baebe 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -84,20 +84,20 @@ func pullFlags(cmd *cobra.Command) { flags.StringVar(&pullOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") _ = cmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) - overrideArchFlagName := "override-arch" - flags.StringVar(&pullOptions.OverrideArch, overrideArchFlagName, "", "Use `ARCH` instead of the architecture of the machine for choosing images") - _ = cmd.RegisterFlagCompletionFunc(overrideArchFlagName, completion.AutocompleteNone) + archFlagName := "arch" + flags.StringVar(&pullOptions.Arch, archFlagName, "", "Use `ARCH` instead of the architecture of the machine for choosing images") + _ = cmd.RegisterFlagCompletionFunc(archFlagName, completion.AutocompleteArch) - overrideOsFlagName := "override-os" - flags.StringVar(&pullOptions.OverrideOS, overrideOsFlagName, "", "Use `OS` instead of the running OS for choosing images") - _ = cmd.RegisterFlagCompletionFunc(overrideOsFlagName, completion.AutocompleteNone) + osFlagName := "os" + flags.StringVar(&pullOptions.OS, osFlagName, "", "Use `OS` instead of the running OS for choosing images") + _ = cmd.RegisterFlagCompletionFunc(osFlagName, completion.AutocompleteOS) - overrideVariantFlagName := "override-variant" - flags.StringVar(&pullOptions.OverrideVariant, overrideVariantFlagName, "", " use VARIANT instead of the running architecture variant for choosing images") - _ = cmd.RegisterFlagCompletionFunc(overrideVariantFlagName, completion.AutocompleteNone) + variantFlagName := "variant" + flags.StringVar(&pullOptions.Variant, variantFlagName, "", " use VARIANT instead of the running architecture variant for choosing images") + _ = cmd.RegisterFlagCompletionFunc(variantFlagName, completion.AutocompleteNone) platformFlagName := "platform" - flags.String(platformFlagName, "", "Specify the platform for selecting the image. (Conflicts with override-arch and override-os)") + flags.String(platformFlagName, "", "Specify the platform for selecting the image. (Conflicts with arch and os)") _ = cmd.RegisterFlagCompletionFunc(platformFlagName, completion.AutocompleteNone) flags.Bool("disable-content-trust", false, "This is a Docker specific option and is a NOOP") @@ -138,13 +138,13 @@ func imagePull(cmd *cobra.Command, args []string) error { return err } if platform != "" { - if pullOptions.OverrideArch != "" || pullOptions.OverrideOS != "" { - return errors.Errorf("--platform option can not be specified with --override-arch or --override-os") + if pullOptions.Arch != "" || pullOptions.OS != "" { + return errors.Errorf("--platform option can not be specified with --arch or --os") } split := strings.SplitN(platform, "/", 2) - pullOptions.OverrideOS = split[0] + pullOptions.OS = split[0] if len(split) > 1 { - pullOptions.OverrideArch = split[1] + pullOptions.Arch = split[1] } } diff --git a/cmd/podman/manifest/add.go b/cmd/podman/manifest/add.go index cb0838eeb..b33f01c10 100644 --- a/cmd/podman/manifest/add.go +++ b/cmd/podman/manifest/add.go @@ -52,7 +52,7 @@ func init() { archFlagName := "arch" flags.StringVar(&manifestAddOpts.Arch, archFlagName, "", "override the `architecture` of the specified image") - _ = addCmd.RegisterFlagCompletionFunc(archFlagName, completion.AutocompleteNone) + _ = addCmd.RegisterFlagCompletionFunc(archFlagName, completion.AutocompleteArch) authfileFlagName := "authfile" flags.StringVar(&manifestAddOpts.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") @@ -72,7 +72,7 @@ func init() { osFlagName := "os" flags.StringVar(&manifestAddOpts.OS, osFlagName, "", "override the `OS` of the specified image") - _ = addCmd.RegisterFlagCompletionFunc(osFlagName, completion.AutocompleteNone) + _ = addCmd.RegisterFlagCompletionFunc(osFlagName, completion.AutocompleteOS) osVersionFlagName := "os-version" flags.StringVar(&manifestAddOpts.OSVersion, osVersionFlagName, "", "override the OS `version` of the specified image") diff --git a/cmd/podman/manifest/annotate.go b/cmd/podman/manifest/annotate.go index 71017e0ec..7c4f5ad01 100644 --- a/cmd/podman/manifest/annotate.go +++ b/cmd/podman/manifest/annotate.go @@ -39,7 +39,7 @@ func init() { archFlagName := "arch" flags.StringVar(&manifestAnnotateOpts.Arch, archFlagName, "", "override the `architecture` of the specified image") - _ = annotateCmd.RegisterFlagCompletionFunc(archFlagName, completion.AutocompleteNone) + _ = annotateCmd.RegisterFlagCompletionFunc(archFlagName, completion.AutocompleteArch) featuresFlagName := "features" flags.StringSliceVar(&manifestAnnotateOpts.Features, featuresFlagName, nil, "override the `features` of the specified image") @@ -47,7 +47,7 @@ func init() { osFlagName := "os" flags.StringVar(&manifestAnnotateOpts.OS, osFlagName, "", "override the `OS` of the specified image") - _ = annotateCmd.RegisterFlagCompletionFunc(osFlagName, completion.AutocompleteNone) + _ = annotateCmd.RegisterFlagCompletionFunc(osFlagName, completion.AutocompleteOS) osFeaturesFlagName := "os-features" flags.StringSliceVar(&manifestAnnotateOpts.OSFeatures, osFeaturesFlagName, nil, "override the OS `features` of the specified image") diff --git a/cmd/podman/utils/alias.go b/cmd/podman/utils/alias.go index 469233b59..8d089920b 100644 --- a/cmd/podman/utils/alias.go +++ b/cmd/podman/utils/alias.go @@ -25,6 +25,12 @@ func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName { name = "external" case "purge": name = "rm" + case "override-arch": + name = "arch" + case "override-os": + name = "os" + case "override-variant": + name = "variant" } return pflag.NormalizedName(name) } diff --git a/contrib/cirrus/runner.sh b/contrib/cirrus/runner.sh index d9f91c7af..50bc1102f 100755 --- a/contrib/cirrus/runner.sh +++ b/contrib/cirrus/runner.sh @@ -130,20 +130,70 @@ exec_container() { } function _run_swagger() { + local upload_filename + local upload_bucket local download_url + local envvarsfile + req_env_vars GCPJSON GCPNAME GCPPROJECT CTR_FQIN + # Building this is a PITA, just grab binary for use in automation # Ref: https://goswagger.io/install.html#static-binary download_url=$(\ curl -s https://api.github.com/repos/go-swagger/go-swagger/releases/latest | \ jq -r '.assets[] | select(.name | contains("linux_amd64")) | .browser_download_url') - curl -o /usr/local/bin/swagger -L'#' "$download_url" + + # The filename and bucket depend on the automation context + #shellcheck disable=SC2154,SC2153 + if [[ -n "$CIRRUS_PR" ]]; then + upload_bucket="libpod-pr-releases" + upload_filename="swagger-pr$CIRRUS_PR.yaml" + elif [[ -n "$CIRRUS_TAG" ]]; then + upload_bucket="libpod-master-releases" + upload_filename="swagger-$CIRRUS_TAG.yaml" + elif [[ "$CIRRUS_BRANCH" == "master" ]]; then + upload_bucket="libpod-master-releases" + # readthedocs versioning uses "latest" for "master" (default) branch + upload_filename="swagger-latest.yaml" + elif [[ -n "$CIRRUS_BRANCH" ]]; then + upload_bucket="libpod-master-releases" + upload_filename="swagger-$CIRRUS_BRANCH.yaml" + else + die "Unknown execution context, expected a non-empty value for \$CIRRUS_TAG, \$CIRRUS_BRANCH, or \$CIRRUS_PR" + fi + + curl -s -o /usr/local/bin/swagger -L'#' "$download_url" chmod +x /usr/local/bin/swagger + # Swagger validation takes a significant amount of time + msg "Pulling \$CTR_FQIN '$CTR_FQIN' (background process)" + podman pull --quiet $CTR_FQIN & + cd $GOSRC make swagger # Cirrus-CI Artifact instruction expects file here - cp -v $GOSRC/pkg/api/swagger.yaml $GOSRC/ + cp -v $GOSRC/pkg/api/swagger.yaml ./ + + envvarsfile=$(mktemp -p '' .tmp_$(basename $0)_XXXXXXXX) + trap "rm -f $envvarsfile" EXIT # contains secrets + # Warning: These values must _not_ be quoted, podman will not remove them. + #shellcheck disable=SC2154 + cat <<eof>>$envvarsfile +GCPJSON=$GCPJSON +GCPNAME=$GCPNAME +GCPPROJECT=$GCPPROJECT +FROM_FILEPATH=$GOSRC/swagger.yaml +TO_GCSURI=gs://$upload_bucket/$upload_filename +eof + + msg "Waiting for backgrounded podman pull to complete..." + wait %% + podman run -it --rm --security-opt label=disable \ + --env-file=$envvarsfile \ + -v $GOSRC:$GOSRC:ro \ + --workdir $GOSRC \ + $CTR_FQIN + rm -f $envvarsfile } function _run_consistency() { diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 7b49caba0..9267b8a1c 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -202,7 +202,6 @@ case "$TEST_FLAVOR" in int) ;& sys) ;& bindings) ;& - swagger) ;& endpoint) # Use existing host bits when testing is to happen inside a container # since this script will run again in that environment. @@ -214,6 +213,7 @@ case "$TEST_FLAVOR" in install_test_configs ;; + swagger) ;& # use next item consistency) make clean ;; release) ;; *) die_unknown TEST_FLAVOR diff --git a/docs/source/_static/api.html b/docs/source/_static/api.html index 11fbb85a0..fbc945d87 100644 --- a/docs/source/_static/api.html +++ b/docs/source/_static/api.html @@ -18,7 +18,7 @@ </style> </head> <body> - <redoc spec-url='https://storage.googleapis.com/libpod-master-releases/swagger-latest-master.yaml' sort-props-alphabetically></redoc> + <redoc spec-url='https://storage.googleapis.com/libpod-master-releases/swagger-latest.yaml' sort-props-alphabetically></redoc> <script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script> </body> </html> diff --git a/docs/source/index.rst b/docs/source/index.rst index 9a1381080..3f3fa10d5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,7 +6,9 @@ Podman_ is a daemonless, open source, Linux native tool designed to make it easy Containers under the control of Podman can either be run by root or by a non-privileged user. Podman manages the entire container ecosystem which includes pods, containers, container images, and container volumes using the libpod_ library. Podman specializes in all of the commands and functions that help you to maintain and modify OCI container images, such as pulling and tagging. It allows you to create, run, and maintain those containers and container images in a production environment. -The Podman service runs only on Linux platforms, however a REST API and clients are currently under development which will allow Mac and Windows platforms to call the service. There is currently a RESTful based remote client which runs on Mac or Windows platforms that allows the remote client to talk to the Podman server on a Linux platform. In addition to those clients, there is also a Mac client. +There is a RESTFul API to manage containers. We also have a remote Podman client that can interact with +the RESTFul service. We currently support clients on Linux, Mac, and Windows. The RESTFul service is only +supported on Linux. If you are completely new to containers, we recommend that you check out the :doc:`Introduction`. For power users or those coming from Docker, check out our :doc:`Tutorials`. For advanced users and contributors, you can get very detailed information about the Podman CLI by looking at our :doc:`Commands` page. Finally, for Developers looking at how to interact with the Podman API, please see our API documentation :doc:`Reference`. diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 35d8474a6..b3ac8b6ee 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -77,6 +77,9 @@ option can be set multiple times. Add an annotation to the container. The format is key=value. The **--annotation** option can be set multiple times. +#### **--arch**=*ARCH* +Override the architecture, defaults to hosts, of the image to be pulled. For example, `arm`. + #### **--attach**, **-a**=*location* Attach to STDIN, STDOUT or STDERR. @@ -667,15 +670,9 @@ Whether to disable OOM Killer for the container or not. Tune the host's OOM preferences for containers (accepts -1000 to 1000) -#### **--override-arch**=*ARCH* -Override the architecture, defaults to hosts, of the image to be pulled. For example, `arm`. - -#### **--override-os**=*OS* +#### **--os**=*OS* Override the OS, defaults to hosts, of the image to be pulled. For example, `windows`. -#### **--override-variant**=*VARIANT* -Use _VARIANT_ instead of the default architecture variant of the container image. Some images can use multiple variants of the arm architectures, such as arm/v5 and arm/v7. - #### **--pid**=*pid* Set the PID mode for the container @@ -691,7 +688,7 @@ Tune the container's pids limit. Set `0` to have unlimited pids for the containe #### **--platform**=*OS/ARCH* -Specify the platform for selecting the image. (Conflicts with override-arch and override-os) +Specify the platform for selecting the image. (Conflicts with --arch and --os) The `--platform` option can be used to override the current architecture and operating system. #### **--pod**=*name* @@ -1007,6 +1004,9 @@ Set the UTS namespace mode for the container. The following values are supported - **ns:[path]**: run the container in the given existing UTS namespace. - **container:[container]**: join the UTS namespace of the specified container. +#### **--variant**=*VARIANT* +Use _VARIANT_ instead of the default architecture variant of the container image. Some images can use multiple variants of the arm architectures, such as arm/v5 and arm/v7. + #### **--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, Podman diff --git a/docs/source/markdown/podman-pull.1.md b/docs/source/markdown/podman-pull.1.md index 44a7e83b6..af91a59e9 100644 --- a/docs/source/markdown/podman-pull.1.md +++ b/docs/source/markdown/podman-pull.1.md @@ -71,6 +71,9 @@ All tagged images in the repository will be pulled. Note: When using the all-tags flag, Podman will not iterate over the search registries in the containers-registries.conf(5) but will always use docker.io for unqualified image names. +#### **--arch**=*ARCH* +Override the architecture, defaults to hosts, of the image to be pulled. For example, `arm`. + #### **--authfile**=*path* Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`. @@ -96,19 +99,16 @@ This is a Docker specific option to disable image verification to a Docker registry and is not supported by Podman. This flag is a NOOP and provided solely for scripting compatibility. -#### **--override-arch**=*ARCH* -Override the architecture, defaults to hosts, of the image to be pulled. For example, `arm`. - -#### **--override-os**=*OS* -Override the OS, defaults to hosts, of the image to be pulled. For example, `windows`. +#### **--help**, **-h** -#### **--override-variant**=*VARIANT* +Print usage statement -Use _VARIANT_ instead of the default architecture variant of the container image. Some images can use multiple variants of the arm architectures, such as arm/v5 and arm/v7. +#### **--os**=*OS* +Override the OS, defaults to hosts, of the image to be pulled. For example, `windows`. #### **--platform**=*OS/ARCH* -Specify the platform for selecting the image. (Conflicts with override-arch and override-os) +Specify the platform for selecting the image. (Conflicts with --arch and --os) The `--platform` option can be used to override the current architecture and operating system. #### **--quiet**, **-q** @@ -121,9 +121,9 @@ Require HTTPS and verify certificates when contacting registries (default: 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. -#### **--help**, **-h** +#### **--variant**=*VARIANT* -Print usage statement +Use _VARIANT_ instead of the default architecture variant of the container image. Some images can use multiple variants of the arm architectures, such as arm/v5 and arm/v7. ## EXAMPLES @@ -189,7 +189,7 @@ Storing signatures ``` ``` -$ podman pull --override-arch=arm arm32v7/debian:stretch +$ podman pull --arch=arm arm32v7/debian:stretch Trying to pull docker.io/arm32v7/debian:stretch... Getting image source signatures Copying blob b531ae4a3925 done diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 89f05c308..e606c456f 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -93,6 +93,9 @@ This option can be set multiple times. Add an annotation to the container. This option can be set multiple times. +#### **--arch**=*ARCH* +Override the architecture, defaults to hosts, of the image to be pulled. For example, `arm`. + #### **--attach**, **-a**=**stdin**|**stdout**|**stderr** Attach to STDIN, STDOUT or STDERR. @@ -704,15 +707,9 @@ Whether to disable OOM Killer for the container or not. Tune the host's OOM preferences for containers (accepts values from **-1000** to **1000**). -#### **--override-arch**=*ARCH* -Override the architecture, defaults to hosts, of the image to be pulled. For example, `arm`. - -#### **--override-os**=*OS* +#### **--os**=*OS* Override the OS, defaults to hosts, of the image to be pulled. For example, `windows`. -#### **--override-variant**=*VARIANT* -Use _VARIANT_ instead of the default architecture variant of the container image. Some images can use multiple variants of the arm architectures, such as arm/v5 and arm/v7. - #### **--pid**=*mode* Set the PID namespace mode for the container. @@ -729,7 +726,7 @@ Tune the container's pids limit. Set to **0** to have unlimited pids for the con #### **--platform**=*OS/ARCH* -Specify the platform for selecting the image. (Conflicts with override-arch and override-os) +Specify the platform for selecting the image. (Conflicts with --arch and --os) The `--platform` option can be used to override the current architecture and operating system. #### **--pod**=*name* @@ -1082,6 +1079,9 @@ Set the UTS namespace mode for the container. The following values are supported - **ns:[path]**: run the container in the given existing UTS namespace. - **container:[container]**: join the UTS namespace of the specified container. +#### **--variant**=*VARIANT* +Use _VARIANT_ instead of the default architecture variant of the container image. Some images can use multiple variants of the arm architectures, such as arm/v5 and arm/v7. + #### **--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] Create a bind mount. If you specify _/HOST-DIR_:_/CONTAINER-DIR_, Podman diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md index 733fadc30..0bb8e387b 100644 --- a/docs/source/markdown/podman.1.md +++ b/docs/source/markdown/podman.1.md @@ -328,7 +328,7 @@ Currently the slirp4netns package is required to be installed to create a networ ### **NOTE:** Unsupported file systems in rootless mode -The Overlay file system (OverlayFS) is not supported in rootless mode. The fuse-overlayfs package is a tool that provides the functionality of OverlayFS in user namespace that allows mounting file systems in rootless environments. It is recommended to install the fuse-overlayfs package and to enable it by adding `mount_program = "/usr/bin/fuse-overlayfs"` under `[storage.options]` in the `$HOME/.config/containers/storage.conf` file. +The Overlay file system (OverlayFS) is not supported in rootless mode. The fuse-overlayfs package is a tool that provides the functionality of OverlayFS in user namespace that allows mounting file systems in rootless environments. It is recommended to install the fuse-overlayfs package. In rootless mode Podman will automatically use the fuse-overlafs program as the mount_program if installed, as long as the $HOME/.config/containers/storage.conf file was not previously created. If storage.conf exists in the homedir, add `mount_program = "/usr/bin/fuse-overlayfs"` under `[storage.options.overlay]` to enable this feature. The Network File System (NFS) and other distributed file systems (for example: Lustre, Spectrum Scale, the General Parallel File System (GPFS)) are not supported when running in rootless mode as these file systems do not understand user namespace. However, rootless Podman can make use of an NFS Homedir by modifying the `$HOME/.config/containers/storage.conf` to have the `graphroot` option point to a directory stored on local (Non NFS) storage. diff --git a/libpod/container_api.go b/libpod/container_api.go index 0d62a2dd7..951227a4f 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -776,3 +776,32 @@ func (c *Container) ShouldRestart(ctx context.Context) bool { } return c.shouldRestart() } + +// ResolvePath resolves the specified path on the root for the container. The +// root must either be the mounted image of the container or the already +// mounted container storage. +// +// It returns the resolved root and the resolved path. Note that the path may +// resolve to the container's mount point or to a volume or bind mount. +func (c *Container) ResolvePath(ctx context.Context, root string, path string) (string, string, error) { + logrus.Debugf("Resolving path %q (root %q) on container %s", path, root, c.ID()) + + // Minimal sanity checks. + if len(root)*len(path) == 0 { + return "", "", errors.Wrapf(define.ErrInternal, "ResolvePath: root (%q) and path (%q) must be non empty", root, path) + } + if _, err := os.Stat(root); err != nil { + return "", "", errors.Wrapf(err, "cannot locate root to resolve path on container %s", c.ID()) + } + + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return "", "", err + } + } + + return c.resolvePath(root, path) +} diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index b41a3fa38..6c9489a08 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -174,25 +174,60 @@ func (c *Container) prepare() error { return err } - // Ensure container entrypoint is created (if required) - if c.config.CreateWorkingDir { - workdir, err := securejoin.SecureJoin(c.state.Mountpoint, c.WorkingDir()) - if err != nil { - return errors.Wrapf(err, "error creating path to container %s working dir", c.ID()) - } - rootUID := c.RootUID() - rootGID := c.RootGID() + // Make sure the workdir exists + if err := c.resolveWorkDir(); err != nil { + return err + } - if err := os.MkdirAll(workdir, 0755); err != nil { - if os.IsExist(err) { - return nil + return nil +} + +// resolveWorkDir resolves the container's workdir and, depending on the +// configuration, will create it, or error out if it does not exist. +// Note that the container must be mounted before. +func (c *Container) resolveWorkDir() error { + workdir := c.WorkingDir() + + // If the specified workdir is a subdir of a volume or mount, + // we don't need to do anything. The runtime is taking care of + // that. + if isPathOnVolume(c, workdir) || isPathOnBindMount(c, workdir) { + logrus.Debugf("Workdir %q resolved to a volume or mount", workdir) + return nil + } + + _, resolvedWorkdir, err := c.resolvePath(c.state.Mountpoint, workdir) + if err != nil { + return err + } + logrus.Debugf("Workdir %q resolved to host path %q", workdir, resolvedWorkdir) + + // No need to create it (e.g., `--workdir=/foo`), so let's make sure + // the path exists on the container. + if !c.config.CreateWorkingDir { + if _, err := os.Stat(resolvedWorkdir); err != nil { + if os.IsNotExist(err) { + return errors.Errorf("workdir %q does not exist on container %s", workdir, c.ID()) } - return errors.Wrapf(err, "error creating container %s working dir", c.ID()) + // This might be a serious error (e.g., permission), so + // we need to return the full error. + return errors.Wrapf(err, "error detecting workdir %q on container %s", workdir, c.ID()) } + } + + // Ensure container entrypoint is created (if required). + rootUID := c.RootUID() + rootGID := c.RootGID() - if err := os.Chown(workdir, rootUID, rootGID); err != nil { - return errors.Wrapf(err, "error chowning container %s working directory to container root", c.ID()) + if err := os.MkdirAll(resolvedWorkdir, 0755); err != nil { + if os.IsExist(err) { + return nil } + return errors.Wrapf(err, "error creating container %s workdir", c.ID()) + } + + if err := os.Chown(resolvedWorkdir, rootUID, rootGID); err != nil { + return errors.Wrapf(err, "error chowning container %s workdir to container root", c.ID()) } return nil diff --git a/libpod/container_path_resolution.go b/libpod/container_path_resolution.go new file mode 100644 index 000000000..805b3b947 --- /dev/null +++ b/libpod/container_path_resolution.go @@ -0,0 +1,166 @@ +package libpod + +import ( + "path/filepath" + "strings" + + securejoin "github.com/cyphar/filepath-securejoin" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// resolveContainerPaths resolves the container's mount point and the container +// path as specified by the user. Both may resolve to paths outside of the +// container's mount point when the container path hits a volume or bind mount. +// +// It returns a bool, indicating whether containerPath resolves outside of +// mountPoint (e.g., via a mount or volume), the resolved root (e.g., container +// mount, bind mount or volume) and the resolved path on the root (absolute to +// the host). +func (container *Container) resolvePath(mountPoint string, containerPath string) (string, string, error) { + // Let's first make sure we have a path relative to the mount point. + pathRelativeToContainerMountPoint := containerPath + if !filepath.IsAbs(containerPath) { + // If the containerPath is not absolute, it's relative to the + // container's working dir. To be extra careful, let's first + // join the working dir with "/", and the add the containerPath + // to it. + pathRelativeToContainerMountPoint = filepath.Join(filepath.Join("/", container.WorkingDir()), containerPath) + } + resolvedPathOnTheContainerMountPoint := filepath.Join(mountPoint, pathRelativeToContainerMountPoint) + pathRelativeToContainerMountPoint = strings.TrimPrefix(pathRelativeToContainerMountPoint, mountPoint) + pathRelativeToContainerMountPoint = filepath.Join("/", pathRelativeToContainerMountPoint) + + // Now we have an "absolute container Path" but not yet resolved on the + // host (e.g., "/foo/bar/file.txt"). As mentioned above, we need to + // check if "/foo/bar/file.txt" is on a volume or bind mount. To do + // that, we need to walk *down* the paths to the root. Assuming + // volume-1 is mounted to "/foo" and volume-2 is mounted to "/foo/bar", + // we must select "/foo/bar". Once selected, we need to rebase the + // remainder (i.e, "/file.txt") on the volume's mount point on the + // host. Same applies to bind mounts. + + searchPath := pathRelativeToContainerMountPoint + for { + volume, err := findVolume(container, searchPath) + if err != nil { + return "", "", err + } + if volume != nil { + logrus.Debugf("Container path %q resolved to volume %q on path %q", containerPath, volume.Name(), searchPath) + + // TODO: We really need to force the volume to mount + // before doing this, but that API is not exposed + // externally right now and doing so is beyond the scope + // of this commit. + mountPoint, err := volume.MountPoint() + if err != nil { + return "", "", err + } + if mountPoint == "" { + return "", "", errors.Errorf("volume %s is not mounted, cannot copy into it", volume.Name()) + } + + // We found a matching volume for searchPath. We now + // need to first find the relative path of our input + // path to the searchPath, and then join it with the + // volume's mount point. + pathRelativeToVolume := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath) + absolutePathOnTheVolumeMount, err := securejoin.SecureJoin(mountPoint, pathRelativeToVolume) + if err != nil { + return "", "", err + } + return mountPoint, absolutePathOnTheVolumeMount, nil + } + + if mount := findBindMount(container, searchPath); mount != nil { + logrus.Debugf("Container path %q resolved to bind mount %q:%q on path %q", containerPath, mount.Source, mount.Destination, searchPath) + // We found a matching bind mount for searchPath. We + // now need to first find the relative path of our + // input path to the searchPath, and then join it with + // the source of the bind mount. + pathRelativeToBindMount := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath) + absolutePathOnTheBindMount, err := securejoin.SecureJoin(mount.Source, pathRelativeToBindMount) + if err != nil { + return "", "", err + } + return mount.Source, absolutePathOnTheBindMount, nil + + } + + if searchPath == "/" { + // Cannot go beyond "/", so we're done. + break + } + + // Walk *down* the path (e.g., "/foo/bar/x" -> "/foo/bar"). + searchPath = filepath.Dir(searchPath) + } + + // No volume, no bind mount but just a normal path on the container. + return mountPoint, resolvedPathOnTheContainerMountPoint, nil +} + +// findVolume checks if the specified containerPath matches the destination +// path of a Volume. Returns a matching Volume or nil. +func findVolume(c *Container, containerPath string) (*Volume, error) { + runtime := c.Runtime() + cleanedContainerPath := filepath.Clean(containerPath) + for _, vol := range c.Config().NamedVolumes { + if cleanedContainerPath == filepath.Clean(vol.Dest) { + return runtime.GetVolume(vol.Name) + } + } + return nil, nil +} + +// isPathOnVolume returns true if the specified containerPath is a subdir of any +// Volume's destination. +func isPathOnVolume(c *Container, containerPath string) bool { + cleanedContainerPath := filepath.Clean(containerPath) + for _, vol := range c.Config().NamedVolumes { + if cleanedContainerPath == filepath.Clean(vol.Dest) { + return true + } + for dest := vol.Dest; dest != "/"; dest = filepath.Dir(dest) { + if cleanedContainerPath == dest { + return true + } + } + } + return false +} + +// findBindMounts checks if the specified containerPath matches the destination +// path of a Mount. Returns a matching Mount or nil. +func findBindMount(c *Container, containerPath string) *specs.Mount { + cleanedPath := filepath.Clean(containerPath) + for _, m := range c.Config().Spec.Mounts { + if m.Type != "bind" { + continue + } + if cleanedPath == filepath.Clean(m.Destination) { + mount := m + return &mount + } + } + return nil +} + +/// isPathOnBindMount returns true if the specified containerPath is a subdir of any +// Mount's destination. +func isPathOnBindMount(c *Container, containerPath string) bool { + cleanedContainerPath := filepath.Clean(containerPath) + for _, m := range c.Config().Spec.Mounts { + if cleanedContainerPath == filepath.Clean(m.Destination) { + return true + } + for dest := m.Destination; dest != "/"; dest = filepath.Dir(dest) { + if cleanedContainerPath == dest { + return true + } + } + } + return false +} diff --git a/libpod/image/search.go b/libpod/image/search.go index 6020fbca9..c5799219a 100644 --- a/libpod/image/search.go +++ b/libpod/image/search.go @@ -102,8 +102,8 @@ func SearchImages(term string, options SearchOptions) ([]SearchResult, error) { searchImageInRegistryHelper := func(index int, registry string) { defer sem.Release(1) defer wg.Done() - searchOutput := searchImageInRegistry(term, registry, options) - data[index] = searchOutputData{data: searchOutput} + searchOutput, err := searchImageInRegistry(term, registry, options) + data[index] = searchOutputData{data: searchOutput, err: err} } ctx := context.Background() @@ -116,13 +116,21 @@ func SearchImages(term string, options SearchOptions) ([]SearchResult, error) { wg.Wait() results := []SearchResult{} + var lastError error for _, d := range data { if d.err != nil { - return nil, d.err + if lastError != nil { + logrus.Errorf("%v", lastError) + } + lastError = d.err + continue } results = append(results, d.data...) } - return results, nil + if len(results) > 0 { + return results, nil + } + return results, lastError } // getRegistries returns the list of registries to search, depending on an optional registry specification @@ -140,7 +148,7 @@ func getRegistries(registry string) ([]string, error) { return registries, nil } -func searchImageInRegistry(term string, registry string, options SearchOptions) []SearchResult { +func searchImageInRegistry(term string, registry string, options SearchOptions) ([]SearchResult, error) { // Max number of queries by default is 25 limit := maxQueries if options.Limit > 0 { @@ -156,16 +164,14 @@ func searchImageInRegistry(term string, registry string, options SearchOptions) if options.ListTags { results, err := searchRepositoryTags(registry, term, sc, options) if err != nil { - logrus.Errorf("error listing registry tags %q: %v", registry, err) - return []SearchResult{} + return []SearchResult{}, err } - return results + return results, nil } results, err := docker.SearchRegistry(context.TODO(), sc, registry, term, limit) if err != nil { - logrus.Errorf("error searching registry %q: %v", registry, err) - return []SearchResult{} + return []SearchResult{}, err } index := registry arr := strings.Split(registry, ".") @@ -219,7 +225,7 @@ func searchImageInRegistry(term string, registry string, options SearchOptions) } paramsArr = append(paramsArr, params) } - return paramsArr + return paramsArr, nil } func searchRepositoryTags(registry, term string, sc *types.SystemContext, options SearchOptions) ([]SearchResult, error) { diff --git a/pkg/api/handlers/compat/images_search.go b/pkg/api/handlers/compat/images_search.go index 6808cdad5..885112b31 100644 --- a/pkg/api/handlers/compat/images_search.go +++ b/pkg/api/handlers/compat/images_search.go @@ -1,25 +1,30 @@ package compat import ( + "fmt" "net/http" - "strconv" "github.com/containers/image/v5/types" - "github.com/containers/podman/v2/libpod/image" + "github.com/containers/podman/v2/libpod" + "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/api/handlers/utils" "github.com/containers/podman/v2/pkg/auth" - "github.com/docker/docker/api/types/registry" + "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/domain/infra/abi" "github.com/gorilla/schema" "github.com/pkg/errors" ) func SearchImages(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { Term string `json:"term"` Limit int `json:"limit"` + NoTrunc bool `json:"noTrunc"` Filters map[string][]string `json:"filters"` TLSVerify bool `json:"tlsVerify"` + ListTags bool `json:"listTags"` }{ // This is where you can override the golang default value for one of fields } @@ -29,67 +34,40 @@ func SearchImages(w http.ResponseWriter, r *http.Request) { return } - filter := image.SearchFilter{} - if len(query.Filters) > 0 { - if len(query.Filters["stars"]) > 0 { - stars, err := strconv.Atoi(query.Filters["stars"][0]) - if err != nil { - utils.InternalServerError(w, err) - return - } - filter.Stars = stars - } - if len(query.Filters["is-official"]) > 0 { - isOfficial, err := strconv.ParseBool(query.Filters["is-official"][0]) - if err != nil { - utils.InternalServerError(w, err) - return - } - filter.IsOfficial = types.NewOptionalBool(isOfficial) - } - if len(query.Filters["is-automated"]) > 0 { - isAutomated, err := strconv.ParseBool(query.Filters["is-automated"][0]) - if err != nil { - utils.InternalServerError(w, err) - return - } - filter.IsAutomated = types.NewOptionalBool(isAutomated) - } - } - options := image.SearchOptions{ - Filter: filter, - 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) + filters := []string{} + for key, val := range query.Filters { + filters = append(filters, fmt.Sprintf("%s=%s", key, val[0])) + } + + options := entities.ImageSearchOptions{ + Authfile: authfile, + Limit: query.Limit, + NoTrunc: query.NoTrunc, + ListTags: query.ListTags, + Filters: filters, + } + if _, found := r.URL.Query()["tlsVerify"]; found { + options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) + } + ir := abi.ImageEngine{Libpod: runtime} + reports, err := ir.Search(r.Context(), query.Term, options) if err != nil { - utils.BadRequest(w, "term", query.Term, err) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return } - - compatResults := make([]registry.SearchResult, 0, len(results)) - for _, result := range results { - compatResult := registry.SearchResult{ - Name: result.Name, - Description: result.Description, - StarCount: result.Stars, - IsAutomated: result.Automated == "[OK]", - IsOfficial: result.Official == "[OK]", + if !utils.IsLibpodRequest(r) { + if len(reports) == 0 { + utils.ImageNotFound(w, query.Term, define.ErrNoSuchImage) + return } - compatResults = append(compatResults, compatResult) } - utils.WriteResponse(w, http.StatusOK, compatResults) + utils.WriteResponse(w, http.StatusOK, reports) } diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 97cd5a65e..3b531652f 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -589,92 +589,6 @@ func UntagImage(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusCreated, "") } -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"` - NoTrunc bool `json:"noTrunc"` - Filters map[string][]string `json:"filters"` - TLSVerify bool `json:"tlsVerify"` - ListTags bool `json:"listTags"` - }{ - // This is where you can override the golang default value for one of fields - } - - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) - return - } - - filter := image.SearchFilter{} - if len(query.Filters) > 0 { - if len(query.Filters["stars"]) > 0 { - stars, err := strconv.Atoi(query.Filters["stars"][0]) - if err != nil { - utils.InternalServerError(w, err) - return - } - filter.Stars = stars - } - if len(query.Filters["is-official"]) > 0 { - isOfficial, err := strconv.ParseBool(query.Filters["is-official"][0]) - if err != nil { - utils.InternalServerError(w, err) - return - } - filter.IsOfficial = types.NewOptionalBool(isOfficial) - } - if len(query.Filters["is-automated"]) > 0 { - isAutomated, err := strconv.ParseBool(query.Filters["is-automated"][0]) - if err != nil { - utils.InternalServerError(w, err) - return - } - filter.IsAutomated = types.NewOptionalBool(isAutomated) - } - } - options := image.SearchOptions{ - Limit: query.Limit, - NoTrunc: query.NoTrunc, - ListTags: query.ListTags, - Filter: filter, - } - - 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 - - searchResults, err := image.SearchImages(query.Term, options) - if err != nil { - utils.BadRequest(w, "term", query.Term, err) - return - } - // Convert from image.SearchResults to entities.ImageSearchReport. We don't - // want to leak any low-level packages into the remote client, which - // requires converting. - reports := make([]entities.ImageSearchReport, len(searchResults)) - for i := range searchResults { - reports[i].Index = searchResults[i].Index - reports[i].Name = searchResults[i].Name - reports[i].Description = searchResults[i].Description - reports[i].Stars = searchResults[i].Stars - reports[i].Official = searchResults[i].Official - reports[i].Automated = searchResults[i].Automated - reports[i].Tag = searchResults[i].Tag - } - - utils.WriteResponse(w, http.StatusOK, reports) -} - // ImagesBatchRemove is the endpoint for batch image removal. func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) diff --git a/pkg/api/handlers/libpod/images_pull.go b/pkg/api/handlers/libpod/images_pull.go index bacba006d..efb15b14d 100644 --- a/pkg/api/handlers/libpod/images_pull.go +++ b/pkg/api/handlers/libpod/images_pull.go @@ -30,12 +30,12 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { - Reference string `schema:"reference"` - OverrideOS string `schema:"overrideOS"` - OverrideArch string `schema:"overrideArch"` - OverrideVariant string `schema:"overrideVariant"` - TLSVerify bool `schema:"tlsVerify"` - AllTags bool `schema:"allTags"` + Reference string `schema:"reference"` + OS string `schema:"OS"` + Arch string `schema:"Arch"` + Variant string `schema:"Variant"` + TLSVerify bool `schema:"tlsVerify"` + AllTags bool `schema:"allTags"` }{ TLSVerify: true, } @@ -83,9 +83,9 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { // Setup the registry options dockerRegistryOptions := image.DockerRegistryOptions{ DockerRegistryCreds: authConf, - OSChoice: query.OverrideOS, - ArchitectureChoice: query.OverrideArch, - VariantChoice: query.OverrideVariant, + OSChoice: query.OS, + ArchitectureChoice: query.Arch, + VariantChoice: query.Variant, } if _, found := r.URL.Query()["tlsVerify"]; found { dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 8d0c0800b..d76f811e9 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -837,13 +837,16 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // name: url // description: Load image from the specified URL // type: string - // - in: formData + // - in: body // name: upload - // type: file // required: true // description: tarball for imported image + // schema: + // type: "string" // produces: // - application/json + // consumes: + // - application/x-tar // responses: // 200: // $ref: "#/responses/DocsLibpodImagesImportResponse" @@ -930,15 +933,15 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: "username:password for the registry" // type: string // - in: query - // name: overrideArch + // name: Arch // description: Pull image for the specified architecture. // type: string // - in: query - // name: overrideOS + // name: OS // description: Pull image for the specified operating system. // type: string // - in: query - // name: overrideVariant + // name: Variant // description: Pull image for the specified variant. // type: string // - in: query @@ -1019,7 +1022,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/DocsSearchResponse" // 500: // $ref: '#/responses/InternalError' - r.Handle(VersionedPath("/libpod/images/search"), s.APIHandler(libpod.SearchImages)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/images/search"), s.APIHandler(compat.SearchImages)).Methods(http.MethodGet) // swagger:operation GET /libpod/images/{name:.*}/get libpod libpodExportImage // --- // tags: diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go index f216dd073..0248f2fa6 100644 --- a/pkg/bindings/images/types.go +++ b/pkg/bindings/images/types.go @@ -171,13 +171,13 @@ type PullOptions struct { Username *string // Password for authenticating against the registry. Password *string - // OverrideArch will overwrite the local architecture for image pulls. - OverrideArch *string - // OverrideOS will overwrite the local operating system (OS) for image + // Arch will overwrite the local architecture for image pulls. + Arch *string + // OS will overwrite the local operating system (OS) for image // pulls. - OverrideOS *string - // OverrideVariant will overwrite the local variant for image pulls. - OverrideVariant *string + OS *string + // Variant will overwrite the local variant for image pulls. + Variant *string // Quiet can be specified to suppress pull progress when pulling. Ignored // for remote calls. Quiet *bool diff --git a/pkg/bindings/images/types_pull_options.go b/pkg/bindings/images/types_pull_options.go index 5163a6341..2bdf2b66e 100644 --- a/pkg/bindings/images/types_pull_options.go +++ b/pkg/bindings/images/types_pull_options.go @@ -168,52 +168,52 @@ func (o *PullOptions) GetPassword() string { return *o.Password } -// WithOverrideArch -func (o *PullOptions) WithOverrideArch(value string) *PullOptions { +// WithArch +func (o *PullOptions) WithArch(value string) *PullOptions { v := &value - o.OverrideArch = v + o.Arch = v return o } -// GetOverrideArch -func (o *PullOptions) GetOverrideArch() string { - var overrideArch string - if o.OverrideArch == nil { - return overrideArch +// GetArch +func (o *PullOptions) GetArch() string { + var arch string + if o.Arch == nil { + return arch } - return *o.OverrideArch + return *o.Arch } -// WithOverrideOS -func (o *PullOptions) WithOverrideOS(value string) *PullOptions { +// WithOS +func (o *PullOptions) WithOS(value string) *PullOptions { v := &value - o.OverrideOS = v + o.OS = v return o } -// GetOverrideOS -func (o *PullOptions) GetOverrideOS() string { - var overrideOS string - if o.OverrideOS == nil { - return overrideOS +// GetOS +func (o *PullOptions) GetOS() string { + var oS string + if o.OS == nil { + return oS } - return *o.OverrideOS + return *o.OS } -// WithOverrideVariant -func (o *PullOptions) WithOverrideVariant(value string) *PullOptions { +// WithVariant +func (o *PullOptions) WithVariant(value string) *PullOptions { v := &value - o.OverrideVariant = v + o.Variant = v return o } -// GetOverrideVariant -func (o *PullOptions) GetOverrideVariant() string { - var overrideVariant string - if o.OverrideVariant == nil { - return overrideVariant +// GetVariant +func (o *PullOptions) GetVariant() string { + var variant string + if o.Variant == nil { + return variant } - return *o.OverrideVariant + return *o.Variant } // WithQuiet diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 78a7d8aa7..ef40d5490 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -133,13 +133,13 @@ type ImagePullOptions struct { Username string // Password for authenticating against the registry. Password string - // OverrideArch will overwrite the local architecture for image pulls. - OverrideArch string - // OverrideOS will overwrite the local operating system (OS) for image + // Arch will overwrite the local architecture for image pulls. + Arch string + // OS will overwrite the local operating system (OS) for image // pulls. - OverrideOS string - // OverrideVariant will overwrite the local variant for image pulls. - OverrideVariant string + OS string + // Variant will overwrite the local variant for image pulls. + Variant string // Quiet can be specified to suppress pull progress when pulling. Ignored // for remote calls. Quiet bool diff --git a/pkg/domain/infra/abi/archive.go b/pkg/domain/infra/abi/archive.go index 809813756..c64dfb02a 100644 --- a/pkg/domain/infra/abi/archive.go +++ b/pkg/domain/infra/abi/archive.go @@ -26,13 +26,18 @@ func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrI return nil, err } + containerMountPoint, err := container.Mount() + if err != nil { + return nil, err + } + unmount := func() { if err := container.Unmount(false); err != nil { logrus.Errorf("Error unmounting container: %v", err) } } - _, resolvedRoot, resolvedContainerPath, err := ic.containerStat(container, containerPath) + _, resolvedRoot, resolvedContainerPath, err := ic.containerStat(container, containerMountPoint, containerPath) if err != nil { unmount() return nil, err @@ -71,6 +76,11 @@ func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID return nil, err } + containerMountPoint, err := container.Mount() + if err != nil { + return nil, err + } + unmount := func() { if err := container.Unmount(false); err != nil { logrus.Errorf("Error unmounting container: %v", err) @@ -83,7 +93,7 @@ func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID containerPath = "/." } - _, resolvedRoot, resolvedContainerPath, err := ic.containerStat(container, containerPath) + _, resolvedRoot, resolvedContainerPath, err := ic.containerStat(container, containerMountPoint, containerPath) if err != nil { unmount() return nil, err diff --git a/pkg/domain/infra/abi/containers_stat.go b/pkg/domain/infra/abi/containers_stat.go index 931e77026..f3d0799a0 100644 --- a/pkg/domain/infra/abi/containers_stat.go +++ b/pkg/domain/infra/abi/containers_stat.go @@ -10,18 +10,11 @@ import ( "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/pkg/copy" "github.com/containers/podman/v2/pkg/domain/entities" - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) -func (ic *ContainerEngine) containerStat(container *libpod.Container, containerPath string) (*entities.ContainerStatReport, string, string, error) { - containerMountPoint, err := container.Mount() - if err != nil { - return nil, "", "", err - } - +func (ic *ContainerEngine) containerStat(container *libpod.Container, containerMountPoint string, containerPath string) (*entities.ContainerStatReport, string, string, error) { // Make sure that "/" copies the *contents* of the mount point and not // the directory. if containerPath == "/" { @@ -30,7 +23,7 @@ func (ic *ContainerEngine) containerStat(container *libpod.Container, containerP // Now resolve the container's path. It may hit a volume, it may hit a // bind mount, it may be relative. - resolvedRoot, resolvedContainerPath, err := resolveContainerPaths(container, containerMountPoint, containerPath) + resolvedRoot, resolvedContainerPath, err := container.ResolvePath(context.Background(), containerMountPoint, containerPath) if err != nil { return nil, "", "", err } @@ -94,138 +87,21 @@ func (ic *ContainerEngine) ContainerStat(ctx context.Context, nameOrID string, c return nil, err } + containerMountPoint, err := container.Mount() + if err != nil { + return nil, err + } + defer func() { if err := container.Unmount(false); err != nil { logrus.Errorf("Error unmounting container: %v", err) } }() - statReport, _, _, err := ic.containerStat(container, containerPath) + statReport, _, _, err := ic.containerStat(container, containerMountPoint, containerPath) return statReport, err } -// resolveContainerPaths resolves the container's mount point and the container -// path as specified by the user. Both may resolve to paths outside of the -// container's mount point when the container path hits a volume or bind mount. -// -// NOTE: We must take volumes and bind mounts into account as, regrettably, we -// can copy to/from stopped containers. In that case, the volumes and bind -// mounts are not present. For running containers, the runtime (e.g., runc or -// crun) takes care of these mounts. For stopped ones, we need to do quite -// some dance, as done below. -func resolveContainerPaths(container *libpod.Container, mountPoint string, containerPath string) (string, string, error) { - // Let's first make sure we have a path relative to the mount point. - pathRelativeToContainerMountPoint := containerPath - if !filepath.IsAbs(containerPath) { - // If the containerPath is not absolute, it's relative to the - // container's working dir. To be extra careful, let's first - // join the working dir with "/", and the add the containerPath - // to it. - pathRelativeToContainerMountPoint = filepath.Join(filepath.Join("/", container.WorkingDir()), containerPath) - } - resolvedPathOnTheContainerMountPoint := filepath.Join(mountPoint, pathRelativeToContainerMountPoint) - pathRelativeToContainerMountPoint = strings.TrimPrefix(pathRelativeToContainerMountPoint, mountPoint) - pathRelativeToContainerMountPoint = filepath.Join("/", pathRelativeToContainerMountPoint) - - // Now we have an "absolute container Path" but not yet resolved on the - // host (e.g., "/foo/bar/file.txt"). As mentioned above, we need to - // check if "/foo/bar/file.txt" is on a volume or bind mount. To do - // that, we need to walk *down* the paths to the root. Assuming - // volume-1 is mounted to "/foo" and volume-2 is mounted to "/foo/bar", - // we must select "/foo/bar". Once selected, we need to rebase the - // remainder (i.e, "/file.txt") on the volume's mount point on the - // host. Same applies to bind mounts. - - searchPath := pathRelativeToContainerMountPoint - for { - volume, err := findVolume(container, searchPath) - if err != nil { - return "", "", err - } - if volume != nil { - logrus.Debugf("Container path %q resolved to volume %q on path %q", containerPath, volume.Name(), searchPath) - - // TODO: We really need to force the volume to mount - // before doing this, but that API is not exposed - // externally right now and doing so is beyond the scope - // of this commit. - mountPoint, err := volume.MountPoint() - if err != nil { - return "", "", err - } - if mountPoint == "" { - return "", "", errors.Errorf("volume %s is not mounted, cannot copy into it", volume.Name()) - } - - // We found a matching volume for searchPath. We now - // need to first find the relative path of our input - // path to the searchPath, and then join it with the - // volume's mount point. - pathRelativeToVolume := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath) - absolutePathOnTheVolumeMount, err := securejoin.SecureJoin(mountPoint, pathRelativeToVolume) - if err != nil { - return "", "", err - } - return mountPoint, absolutePathOnTheVolumeMount, nil - } - - if mount := findBindMount(container, searchPath); mount != nil { - logrus.Debugf("Container path %q resolved to bind mount %q:%q on path %q", containerPath, mount.Source, mount.Destination, searchPath) - // We found a matching bind mount for searchPath. We - // now need to first find the relative path of our - // input path to the searchPath, and then join it with - // the source of the bind mount. - pathRelativeToBindMount := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath) - absolutePathOnTheBindMount, err := securejoin.SecureJoin(mount.Source, pathRelativeToBindMount) - if err != nil { - return "", "", err - } - return mount.Source, absolutePathOnTheBindMount, nil - - } - - if searchPath == "/" { - // Cannot go beyond "/", so we're done. - break - } - - // Walk *down* the path (e.g., "/foo/bar/x" -> "/foo/bar"). - searchPath = filepath.Dir(searchPath) - } - - // No volume, no bind mount but just a normal path on the container. - return mountPoint, resolvedPathOnTheContainerMountPoint, nil -} - -// findVolume checks if the specified container path matches a volume inside -// the container. It returns a matching volume or nil. -func findVolume(c *libpod.Container, containerPath string) (*libpod.Volume, error) { - runtime := c.Runtime() - cleanedContainerPath := filepath.Clean(containerPath) - for _, vol := range c.Config().NamedVolumes { - if cleanedContainerPath == filepath.Clean(vol.Dest) { - return runtime.GetVolume(vol.Name) - } - } - return nil, nil -} - -// findBindMount checks if the specified container path matches a bind mount -// inside the container. It returns a matching mount or nil. -func findBindMount(c *libpod.Container, containerPath string) *specs.Mount { - cleanedPath := filepath.Clean(containerPath) - for _, m := range c.Config().Spec.Mounts { - if m.Type != "bind" { - continue - } - if cleanedPath == filepath.Clean(m.Destination) { - mount := m - return &mount - } - } - return nil -} - // secureStat extracts file info for path in a chroot'ed environment in root. func secureStat(root string, path string) (*buildahCopiah.StatForItem, error) { var glob string diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 1288ab09b..8ca93e770 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -241,9 +241,9 @@ func pull(ctx context.Context, runtime *image.Runtime, rawImage string, options dockerRegistryOptions := image.DockerRegistryOptions{ DockerRegistryCreds: registryCreds, DockerCertPath: options.CertDir, - OSChoice: options.OverrideOS, - ArchitectureChoice: options.OverrideArch, - VariantChoice: options.OverrideVariant, + OSChoice: options.OS, + ArchitectureChoice: options.Arch, + VariantChoice: options.Variant, DockerInsecureSkipTLSVerify: options.SkipTLSVerify, } diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 2d686b2aa..0de756756 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -106,8 +106,8 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, opts entities.ImagePullOptions) (*entities.ImagePullReport, error) { options := new(images.PullOptions) - options.WithAllTags(opts.AllTags).WithAuthfile(opts.Authfile).WithCertDir(opts.CertDir).WithOverrideArch(opts.OverrideArch).WithOverrideOS(opts.OverrideOS) - options.WithOverrideVariant(opts.OverrideVariant).WithPassword(opts.Password).WithPullPolicy(opts.PullPolicy) + options.WithAllTags(opts.AllTags).WithAuthfile(opts.Authfile).WithCertDir(opts.CertDir).WithArch(opts.Arch).WithOS(opts.OS) + options.WithVariant(opts.Variant).WithPassword(opts.Password).WithPullPolicy(opts.PullPolicy) if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { if s == types.OptionalBoolTrue { options.WithSkipTLSVerify(true) diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index cc3f7928c..31d317bf8 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -203,20 +203,6 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat } s.Annotations = annotations - // workdir - if s.WorkDir == "" { - if newImage != nil { - workingDir, err := newImage.WorkingDir(ctx) - if err != nil { - return nil, err - } - s.WorkDir = workingDir - } - } - if s.WorkDir == "" { - s.WorkDir = "/" - } - if len(s.SeccompProfilePath) < 1 { p, err := libpod.DefaultSeccompPath() if err != nil { diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 4f36744ca..1bc050b00 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -272,10 +272,18 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. if s.Entrypoint != nil { options = append(options, libpod.WithEntrypoint(s.Entrypoint)) } - // If the user did not set an workdir but the image did, ensure it is - // created. + // If the user did not specify a workdir on the CLI, let's extract it + // from the image. if s.WorkDir == "" && img != nil { options = append(options, libpod.WithCreateWorkingDir()) + wd, err := img.WorkingDir(ctx) + if err != nil { + return nil, err + } + s.WorkDir = wd + } + if s.WorkDir == "" { + s.WorkDir = "/" } if s.StopSignal != nil { options = append(options, libpod.WithStopSignal(*s.StopSignal)) diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index a4931ff2d..73d92e5a0 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -282,7 +282,7 @@ var _ = Describe("Podman create", func() { }) It("podman create using image list by tag", func() { - session := podmanTest.Podman([]string{"create", "--pull=always", "--override-arch=arm64", "--name=foo", ALPINELISTTAG}) + session := podmanTest.Podman([]string{"create", "--pull=always", "--arch=arm64", "--name=foo", ALPINELISTTAG}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To((Equal(0))) session = podmanTest.Podman([]string{"inspect", "--format", "{{.Image}}", "foo"}) @@ -296,7 +296,7 @@ var _ = Describe("Podman create", func() { }) It("podman create using image list by digest", func() { - session := podmanTest.Podman([]string{"create", "--pull=always", "--override-arch=arm64", "--name=foo", ALPINELISTDIGEST}) + session := podmanTest.Podman([]string{"create", "--pull=always", "--arch=arm64", "--name=foo", ALPINELISTDIGEST}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To((Equal(0))) session = podmanTest.Podman([]string{"inspect", "--format", "{{.Image}}", "foo"}) @@ -310,7 +310,7 @@ var _ = Describe("Podman create", func() { }) It("podman create using image list instance by digest", func() { - session := podmanTest.Podman([]string{"create", "--pull=always", "--override-arch=arm64", "--name=foo", ALPINEARM64DIGEST}) + session := podmanTest.Podman([]string{"create", "--pull=always", "--arch=arm64", "--name=foo", ALPINEARM64DIGEST}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To((Equal(0))) session = podmanTest.Podman([]string{"inspect", "--format", "{{.Image}}", "foo"}) @@ -324,7 +324,7 @@ var _ = Describe("Podman create", func() { }) It("podman create using cross-arch image list instance by digest", func() { - session := podmanTest.Podman([]string{"create", "--pull=always", "--override-arch=arm64", "--name=foo", ALPINEARM64DIGEST}) + session := podmanTest.Podman([]string{"create", "--pull=always", "--arch=arm64", "--name=foo", ALPINEARM64DIGEST}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To((Equal(0))) session = podmanTest.Podman([]string{"inspect", "--format", "{{.Image}}", "foo"}) @@ -652,10 +652,10 @@ var _ = Describe("Podman create", func() { expectedError := "no image found in manifest list for architecture bogus" Expect(session.ErrorToString()).To(ContainSubstring(expectedError)) - session = podmanTest.Podman([]string{"create", "--platform=linux/arm64", "--override-os", "windows", ALPINE}) + session = podmanTest.Podman([]string{"create", "--platform=linux/arm64", "--os", "windows", ALPINE}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(125)) - expectedError = "--platform option can not be specified with --override-arch or --override-os" + expectedError = "--platform option can not be specified with --arch or --os" Expect(session.ErrorToString()).To(ContainSubstring(expectedError)) session = podmanTest.Podman([]string{"create", "-q", "--platform=linux/arm64", ALPINE}) diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go index 7099a2904..4b73004da 100644 --- a/test/e2e/pull_test.go +++ b/test/e2e/pull_test.go @@ -92,7 +92,7 @@ var _ = Describe("Podman pull", func() { }) It("podman pull by digest (image list)", func() { - session := podmanTest.Podman([]string{"pull", "--override-arch=arm64", ALPINELISTDIGEST}) + session := podmanTest.Podman([]string{"pull", "--arch=arm64", ALPINELISTDIGEST}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) // inspect using the digest of the list @@ -135,7 +135,7 @@ var _ = Describe("Podman pull", func() { }) It("podman pull by instance digest (image list)", func() { - session := podmanTest.Podman([]string{"pull", "--override-arch=arm64", ALPINEARM64DIGEST}) + session := podmanTest.Podman([]string{"pull", "--arch=arm64", ALPINEARM64DIGEST}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) // inspect using the digest of the list @@ -175,7 +175,7 @@ var _ = Describe("Podman pull", func() { }) It("podman pull by tag (image list)", func() { - session := podmanTest.Podman([]string{"pull", "--override-arch=arm64", ALPINELISTTAG}) + session := podmanTest.Podman([]string{"pull", "--arch=arm64", ALPINELISTTAG}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) // inspect using the tag we used for pulling @@ -503,10 +503,10 @@ var _ = Describe("Podman pull", func() { expectedError := "no image found in manifest list for architecture bogus" Expect(session.ErrorToString()).To(ContainSubstring(expectedError)) - session = podmanTest.Podman([]string{"pull", "--platform=linux/arm64", "--override-os", "windows", ALPINE}) + session = podmanTest.Podman([]string{"pull", "--platform=linux/arm64", "--os", "windows", ALPINE}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(125)) - expectedError = "--platform option can not be specified with --override-arch or --override-os" + expectedError = "--platform option can not be specified with --arch or --os" Expect(session.ErrorToString()).To(ContainSubstring(expectedError)) session = podmanTest.Podman([]string{"pull", "-q", "--platform=linux/arm64", ALPINE}) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 19060ecdc..caeaf190e 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -669,7 +669,7 @@ USER bin` }) It("podman run with secrets", func() { - SkipIfRemote("--default-mount-file option is not supported in podman-remote") + SkipIfRemote("--default-mounts-file option is not supported in podman-remote") containersDir := filepath.Join(podmanTest.TempDir, "containers") err := os.MkdirAll(containersDir, 0755) Expect(err).To(BeNil()) diff --git a/test/e2e/run_working_dir_test.go b/test/e2e/run_working_dir_test.go index 7d8db361c..59538448e 100644 --- a/test/e2e/run_working_dir_test.go +++ b/test/e2e/run_working_dir_test.go @@ -2,7 +2,6 @@ package integration import ( "os" - "strings" . "github.com/containers/podman/v2/test/utils" . "github.com/onsi/ginkgo" @@ -41,12 +40,9 @@ var _ = Describe("Podman run", func() { }) It("podman run a container using non existing --workdir", func() { - if !strings.Contains(podmanTest.OCIRuntime, "crun") { - Skip("Test only works on crun") - } session := podmanTest.Podman([]string{"run", "--workdir", "/home/foobar", ALPINE, "pwd"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(127)) + Expect(session.ExitCode()).To(Equal(126)) }) It("podman run a container on an image with a workdir", func() { diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index 1d86ae744..4a11802c3 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -299,7 +299,6 @@ registries = ['{{.Host}}:{{.Port}}']` }) It("podman search doesn't attempt HTTP if force secure is true", func() { - SkipIfRemote("FIXME This should work on podman-remote") if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } @@ -324,15 +323,11 @@ 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.Podman([]string{"search", image, "--tls-verify=true"}) search.WaitWithDefaultTimeout() - Expect(search.ExitCode()).To(Equal(0)) + Expect(search.ExitCode()).To(Equal(125)) Expect(search.OutputToString()).Should(BeEmpty()) match, _ := search.ErrorGrepString("error") Expect(match).Should(BeTrue()) @@ -342,7 +337,6 @@ registries = ['{{.Host}}:{{.Port}}']` }) It("podman search doesn't attempt HTTP if registry is not listed as insecure", func() { - SkipIfRemote("FIXME This should work on podman-remote") if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } @@ -376,7 +370,7 @@ registries = ['{{.Host}}:{{.Port}}']` search := podmanTest.Podman([]string{"search", image}) search.WaitWithDefaultTimeout() - Expect(search.ExitCode()).To(Equal(0)) + Expect(search.ExitCode()).To(Equal(125)) Expect(search.OutputToString()).Should(BeEmpty()) match, _ := search.ErrorGrepString("error") Expect(match).Should(BeTrue()) @@ -386,7 +380,6 @@ registries = ['{{.Host}}:{{.Port}}']` }) It("podman search doesn't attempt HTTP if one registry is not listed as insecure", func() { - SkipIfRemote("FIXME This should work on podman-remote") if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } @@ -431,7 +424,7 @@ registries = ['{{.Host}}:{{.Port}}']` search := podmanTest.Podman([]string{"search", "my-alpine"}) search.WaitWithDefaultTimeout() - Expect(search.ExitCode()).To(Equal(0)) + Expect(search.ExitCode()).To(Equal(125)) Expect(search.OutputToString()).Should(BeEmpty()) match, _ := search.ErrorGrepString("error") Expect(match).Should(BeTrue()) diff --git a/test/python/docker/test_images.py b/test/python/docker/test_images.py index 1fa4aade9..f2b6a5190 100644 --- a/test/python/docker/test_images.py +++ b/test/python/docker/test_images.py @@ -82,8 +82,16 @@ class TestImages(unittest.TestCase): def test_search_image(self): """Search for image""" - for r in self.client.images.search("libpod/alpine"): - self.assertIn("quay.io/libpod/alpine", r["Name"]) + for r in self.client.images.search("alpine"): + self.assertIn("alpine", r["Name"]) + + def test_search_bogus_image(self): + """Search for bogus image should throw exception""" + try: + r = self.client.images.search("bogus/bogus") + except: + return + self.assertTrue(len(r)==0) def test_remove_image(self): """Remove image""" diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 29dc95dc3..dcf1da370 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -589,4 +589,25 @@ json-file | f is "${lines[1]}" "$rand" "Container runs successfully despite warning" } +@test "podman run - check workdir" { + # Workdirs specified via the CLI are not created on the root FS. + run_podman 126 run --rm --workdir /i/do/not/exist $IMAGE pwd + # Note: remote error prepends an attach error. + is "$output" "Error: .*workdir \"/i/do/not/exist\" does not exist on container.*" + + testdir=$PODMAN_TMPDIR/volume + mkdir -p $testdir + randomcontent=$(random_string 10) + echo "$randomcontent" > $testdir/content + + # Workdir does not exist on the image but is volume mounted. + run_podman run --rm --workdir /IamNotOnTheImage -v $testdir:/IamNotOnTheImage $IMAGE cat content + is "$output" "$randomcontent" "cat random content" + + # Workdir does not exist on the image but is created by the runtime as it's + # a subdir of a volume. + run_podman run --rm --workdir /IamNotOntheImage -v $testdir/content:/IamNotOntheImage/foo $IMAGE cat foo + is "$output" "$randomcontent" "cat random content" +} + # vim: filetype=sh diff --git a/test/system/070-build.bats b/test/system/070-build.bats index 9e5e3ee1d..7a42a4c18 100644 --- a/test/system/070-build.bats +++ b/test/system/070-build.bats @@ -163,10 +163,12 @@ EOF https_proxy=https-proxy-in-env-file EOF + # NOTE: it's important to not create the workdir. + # Podman will make sure to create a missing workdir + # if needed. See #9040. cat >$tmpdir/Containerfile <<EOF FROM $IMAGE LABEL $label_name=$label_value -RUN mkdir $workdir WORKDIR $workdir # Test for #7094 - chowning of invalid symlinks @@ -188,8 +190,7 @@ ADD mycmd /bin/mydefaultcmd RUN chmod 755 /bin/mydefaultcmd RUN chown 2:3 /bin/mydefaultcmd -#FIXME FIXME FIXME: enable if/when 'podman build' passes mounts.conf to buildah -#RUN $CAT_SECRET +RUN $CAT_SECRET CMD ["/bin/mydefaultcmd","$s_echo"] EOF @@ -201,8 +202,7 @@ EOF local iid="${lines[-1]}" # Make sure 'podman build' had the secret mounted - #FIXME FIXME: enable if/when 'podman build' passes mounts.conf to buildah - #is "$output" ".*$secret_contents.*" "podman build has /run/secrets mounted" + is "$output" ".*$secret_contents.*" "podman build has /run/secrets mounted" if is_remote; then ENVHOST="" |