diff options
54 files changed, 799 insertions, 515 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 074f2f4e0..b77464bae 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -30,7 +30,7 @@ env: PRIOR_UBUNTU_NAME: "ubuntu-2004" # Google-cloud VM Images - IMAGE_SUFFIX: "c5961289315909632" + IMAGE_SUFFIX: "c5501386583441408" FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}" PRIOR_FEDORA_CACHE_IMAGE_NAME: "prior-fedora-${IMAGE_SUFFIX}" UBUNTU_CACHE_IMAGE_NAME: "ubuntu-${IMAGE_SUFFIX}" @@ -242,8 +242,11 @@ bin/podman.cross.%: .gopathok # Update nix/nixpkgs.json its latest stable commit .PHONY: nixpkgs nixpkgs: - @nix run -f channel:nixos-20.09 nix-prefetch-git -c nix-prefetch-git \ - --no-deepClone https://github.com/nixos/nixpkgs > nix/nixpkgs.json + @nix run \ + -f channel:nixos-20.09 nix-prefetch-git \ + -c nix-prefetch-git \ + --no-deepClone \ + https://github.com/nixos/nixpkgs refs/head/nixos-20.09 > nix/nixpkgs.json # Build statically linked binary .PHONY: static diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index dda709ecd..220a30a10 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -576,6 +576,14 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { `If a container with the same name exists, replace it`, ) + requiresFlagName := "requires" + createFlags.StringSliceVar( + &cf.Requires, + requiresFlagName, []string{}, + "Add one or more requirement containers that must be started before this container will start", + ) + _ = cmd.RegisterFlagCompletionFunc(requiresFlagName, AutocompleteContainers) + restartFlagName := "restart" createFlags.StringVar( &cf.Restart, diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index a296ef4f1..e14918fe1 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -93,6 +93,7 @@ type ContainerCLIOpts struct { ReadOnlyTmpFS bool Restart string Replace bool + Requires []string Rm bool RootFS bool Secrets []string diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index d1b67d963..363a8f5f9 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -486,6 +486,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.ReadOnlyFilesystem = c.ReadOnly s.ConmonPidFile = c.ConmonPIDFile + s.DependencyContainers = c.Requires + // TODO // outside of specgen and oci though // defaults to true, check spec/storage diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index 451a267b3..2cd28e34a 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -13,8 +13,12 @@ set -a _waserrexit=0 if [[ "$SHELLOPTS" =~ errexit ]]; then _waserrexit=1; fi set +e # Assumed in F33 for setting global vars -source /etc/profile -source /etc/environment +if [[ -r "/etc/automation_environment" ]]; then + source /etc/automation_environment +else # prior to automation library v2.0, this was necessary + source /etc/profile + source /etc/environment +fi if [[ -r "/etc/ci_environment" ]]; then source /etc/ci_environment; fi USER="$(whoami)" HOME="$(getent passwd $USER | cut -d : -f 6)" diff --git a/contrib/cirrus/runner.sh b/contrib/cirrus/runner.sh index f52e107cc..8d3a6b987 100755 --- a/contrib/cirrus/runner.sh +++ b/contrib/cirrus/runner.sh @@ -269,13 +269,18 @@ function _run_release() { } logformatter() { - # Use similar format as human-friendly task name from .cirrus.yml - # shellcheck disable=SC2154 - output_name="$TEST_FLAVOR-$PODBIN_NAME-$DISTRO_NV-$PRIV_NAME-$TEST_ENVIRON" - # Requires stdin and stderr combined! - cat - \ - |& awk --file "${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/timestamp.awk" \ - |& "${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/logformatter" "$output_name" + if [[ "$CI" == "true" ]]; then + # Use similar format as human-friendly task name from .cirrus.yml + # shellcheck disable=SC2154 + output_name="$TEST_FLAVOR-$PODBIN_NAME-$DISTRO_NV-$PRIV_NAME-$TEST_ENVIRON" + # Requires stdin and stderr combined! + cat - \ + |& awk --file "${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/timestamp.awk" \ + |& "${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/logformatter" "$output_name" + else + # Assume script is run by a human, they want output immediatly + cat - + fi } # Handle local|remote integration|system testing in a uniform way diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 927b3df33..fcd5f3e3f 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -781,6 +781,12 @@ If container is running in --read-only mode, then mount a read-write tmpfs on /r If another container with the same name already exists, replace and remove it. The default is **false**. +#### **\-\-requires**=**container** + +Specify one or more requirements. +A requirement is a dependency container that will be started before this container. +Containers can be specified by name or ID, with multiple containers being separated by commas. + #### **\-\-restart**=*policy* Restart policy to follow when containers exit. @@ -1250,6 +1256,25 @@ $ podman create --tz=Asia/Shanghai alpine date $ podman create --tz=US/Eastern alpine date ``` +### Adding dependency containers + +Podman will make sure the first container, container1, is running before the second container (container2) is started. + +``` +$ podman create --name container1 -t -i fedora bash +$ podman create --name container2 --requires container1 -t -i fedora bash +$ podman start --attach container2 +``` + +Multiple containers can be required. + +``` +$ podman create --name container1 -t -i fedora bash +$ podman create --name container2 -t -i fedora bash +$ podman create --name container3 --requires container1,container2 -t -i fedora bash +$ podman start --attach container3 +``` + ### Rootless Containers Podman runs as a non root user on most systems. This feature requires that a new enough version of shadow-utils @@ -1297,7 +1322,7 @@ b NOTE: Use the environment variable `TMPDIR` to change the temporary storage location of downloaded container images. Podman defaults to use `/var/tmp`. ## SEE ALSO -**podman**(1), **podman-secret**(1), **podman-save**(1), **podman-ps**(1), **podman-attach**(1), **podman-pod-create**(1), **podman-port**(1), **podman-kill**(1), **podman-stop**(1), +**podman**(1), **podman-secret**(1), **podman-save**(1), **podman-ps**(1), **podman-attach**(1), **podman-pod-create**(1), **podman-port**(1), **podman-start*(1), **podman-kill**(1), **podman-stop**(1), **podman-generate-systemd**(1) **podman-rm**(1), **subgid**(5), **subuid**(5), **containers.conf**(5), **systemd.unit**(5), **setsebool**(8), **slirp4netns**(1), **fuse-overlayfs**(1), **proc**(5)**. ## HISTORY diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 4c096ecfe..3fad9bf64 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -825,6 +825,12 @@ If container is running in **\-\-read-only** mode, then mount a read-write tmpfs If another container with the same name already exists, replace and remove it. The default is **false**. +#### **\-\-requires**=**container** + +Specify one or more requirements. +A requirement is a dependency container that will be started before this container. +Containers can be specified by name or ID, with multiple containers being separated by commas. + #### **\-\-restart**=*policy* Restart policy to follow when containers exit. @@ -1612,6 +1618,24 @@ $ podman run --tz=Asia/Shanghai alpine date $ podman run --tz=US/Eastern alpine date ``` +### Adding dependency containers + +The first container, container1, is not started initially, but must be running before container2 will start. +The `podman run` command will start the container automatically before starting container2. + +``` +$ podman create --name container1 -t -i fedora bash +$ podman run --name container2 --requires container1 -t -i fedora bash +``` + +Multiple containers can be required. + +``` +$ podman create --name container1 -t -i fedora bash +$ podman create --name container2 -t -i fedora bash +$ podman run --name container3 --requires container1,container2 -t -i fedora bash +``` + ### Rootless Containers Podman runs as a non root user on most systems. This feature requires that a new enough version of **shadow-utils** @@ -1657,7 +1681,7 @@ b NOTE: Use the environment variable `TMPDIR` to change the temporary storage location of downloaded container images. Podman defaults to use `/var/tmp`. ## SEE ALSO -**podman**(1), **podman-save**(1), **podman-ps**(1), **podman-attach**(1), **podman-pod-create**(1), **podman-port**(1), **podman-kill**(1), **podman-stop**(1), +**podman**(1), **podman-save**(1), **podman-ps**(1), **podman-attach**(1), **podman-pod-create**(1), **podman-port**(1), **podman-start**(1), **podman-kill**(1), **podman-stop**(1), **podman-generate-systemd**(1) **podman-rm**(1), **subgid**(5), **subuid**(5), **containers.conf**(5), **systemd.unit**(5), **setsebool**(8), **slirp4netns**(1), **fuse-overlayfs**(1), **proc**(5)**. ## HISTORY @@ -10,7 +10,7 @@ require ( github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b github.com/containernetworking/cni v0.8.1 github.com/containernetworking/plugins v0.9.1 - github.com/containers/buildah v1.20.0 + github.com/containers/buildah v1.20.1-0.20210402144408-36a37402d0c8 github.com/containers/common v0.35.4 github.com/containers/conmon v2.0.20+incompatible github.com/containers/image/v5 v5.10.5 @@ -174,9 +174,8 @@ github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHV github.com/containernetworking/plugins v0.8.7/go.mod h1:R7lXeZaBzpfqapcAbHRW8/CYwm0dHzbz0XEjofx0uB0= github.com/containernetworking/plugins v0.9.1 h1:FD1tADPls2EEi3flPc2OegIY1M9pUa9r2Quag7HMLV8= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= -github.com/containers/buildah v1.20.0 h1:H8db/d2uSGm947mqjX0Iup6F0T9AnK3kS/ff9RCemZA= -github.com/containers/buildah v1.20.0/go.mod h1:8V3UBoTKBWU9AxNHb1MAKnZZ9oSoz/IsYyjeymrpl1s= -github.com/containers/common v0.35.3/go.mod h1:rMzxgD7nMGw++cEbsp+NZv0UJO4rgXbm7F7IbJPTwIE= +github.com/containers/buildah v1.20.1-0.20210402144408-36a37402d0c8 h1:RlqbDlfE3+qrq4bNTZG7NVPqCDzfZrgE/yicu0VAykQ= +github.com/containers/buildah v1.20.1-0.20210402144408-36a37402d0c8/go.mod h1:iowyscoAC5jwNDhs3c5CLGdBZ9FJk5UOoN2I5TdmXFs= github.com/containers/common v0.35.4 h1:szyWRncsHkBwCVpu1dkEOXUjkwCetlfcLmKJTwo1Sp8= github.com/containers/common v0.35.4/go.mod h1:rMzxgD7nMGw++cEbsp+NZv0UJO4rgXbm7F7IbJPTwIE= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= diff --git a/hack/get_ci_vm.sh b/hack/get_ci_vm.sh index 4f6c42a06..1a4804857 100755 --- a/hack/get_ci_vm.sh +++ b/hack/get_ci_vm.sh @@ -3,270 +3,68 @@ # # For help and usage information, simply execute the script w/o any arguments. # -# This script is intended to be run by podman developers who need to debug -# problems specifically related to Cirrus-CI automated testing. However, -# because it's only loosely coupled to the `.cirrus.yml` configuration, it must -# orchestrate VMs in GCP directly. This means users need to have -# pre-authorization (access) to manipulate google-cloud resources. Additionally, -# there are no guarantees it will remain in-sync with other automation-related -# scripts. Therefore it may not always function for everybody in every -# future scenario without updates/modifications/tweaks. -# -# When successful, you will end up connected to a GCP VM with with a clone of -# the upstream podman repository 'master' branch, using a remote named 'origin'. -# If you want to customize this behavior, you will want to use a "hook" script. -# Please use this example carefully, since git setups vary by person, you -# will probably need to make local edits. -# -# https://gist.github.com/cevich/626a0790c0b476d5cd2a5a76fbdae0a1 +# This script is intended to be run by Red Hat podman developers who need +# to debug problems specifically related to Cirrus-CI automated testing. +# It requires that you have been granted prior access to create VMs in +# google-cloud. For non-Red Hat contributors, VMs are available as-needed, +# with supervision upon request. set -e -RED="\e[1;31m" -YEL="\e[1;32m" -NOR="\e[0m" -USAGE_WARNING=" -${YEL}WARNING: This will not work without podman,${NOR} - ${YEL}and prior authorization to use the libpod GCP project.${NOR} -" -# These values come from .cirrus.yml gce_instance clause -ZONE="${ZONE:-us-central1-a}" -CPUS="2" -MEMORY="4Gb" -DISK="200" -PROJECT="libpod-218412" -GOSRC="/var/tmp/go/src/github.com/containers/podman" -GIT_REPO="https://github.com/containers/podman.git" - -# Container image with necessary runtime elements -GCLOUD_IMAGE="${GCLOUD_IMAGE:-docker.io/google/cloud-sdk:alpine}" -GCLOUD_CFGDIR=".config/gcloud" - -SCRIPT_FILENAME=$(basename ${BASH_SOURCE[0]}) -HOOK_FILENAME="hook_${SCRIPT_FILENAME}" +SCRIPT_FILEPATH=$(realpath "${BASH_SOURCE[0]}") +SCRIPT_DIRPATH=$(dirname "$SCRIPT_FILEPATH") +REPO_DIRPATH=$(realpath "$SCRIPT_DIRPATH/../") -# Shared tmp directory between container and us -TMPDIR=$(mktemp -d --tmpdir ${SCRIPT_FILENAME}_tmpdir_XXXXXX) - -show_usage() { - echo -e "\n${RED}ERROR: $1${NOR}" - echo -e "${YEL}Usage: $SCRIPT_FILENAME <image_name>${NOR}" - echo "" - if [[ -r ".cirrus.yml" ]] - then - echo -e "${YEL}Some possible image_name values (from .cirrus.yml):${NOR}" - image_hints - echo "" - echo -e "${YEL}Optional:${NOR} If a $HOME/$GCLOUD_CFGDIR/$HOOK_FILENAME executable exists during" - echo "VM creation, it will be executed remotely after cloning" - echo "$GIT_REPO. The" - echo "current local working branch name and commit ID, will be provided as" - echo "it's arguments." +# Help detect if we were called by get_ci_vm container +GET_CI_VM="${GET_CI_VM:-0}" +in_get_ci_vm() { + if ((GET_CI_VM==0)); then + echo "Error: $1 is not intended for use in this context" + exit 2 fi - exit 1 -} - -LIBPODROOT=$(realpath "$(dirname ${BASH_SOURCE[0]})/../") -# else: Assume $PWD is the root of the libpod repository -[[ "$LIBPODROOT" != "/" ]] || \ - show_usage "Must execute script from within clone of containers/podman repo." - -[[ "$UID" -ne 0 ]] || \ - show_usage "Must execute script as a regular (non-root) user." - -[[ "${LIBPODROOT#$HOME}" != "$LIBPODROOT" ]] || \ - show_usage "Clone of containers/podman must be a subdirectory of \$HOME ($HOME)" - -# Disable SELinux labeling to allow read-only mounting of repository files -PGCLOUD="podman run -it --rm --security-opt label=disable -v $TMPDIR:$TMPDIR -v $HOME/.config/gcloud:/root/.config/gcloud -v $HOME/.config/gcloud/ssh:/root/.ssh -v $LIBPODROOT:$LIBPODROOT:ro $GCLOUD_IMAGE gcloud --configuration=libpod --project=$PROJECT" -SCP_CMD="$PGCLOUD compute scp" - -showrun() { - echo '+ '$(printf " %q" "$@") > /dev/stderr - echo "" - "$@" -} - -cleanup() { - RET=$? - set +e - wait - - # set GCLOUD_DEBUG to leave tmpdir behind for postmortem - # shellcheck disable=SC2154 - test -z "$GCLOUD_DEBUG" && rm -rf $TMPDIR - - # Not always called from an exit handler, but should always exit when called - exit $RET -} -trap cleanup EXIT - -delvm() { - echo -e "\n" - echo -e "\n${YEL}Offering to Delete $VMNAME${NOR}" - echo -e "${RED}(Deletion might take a minute or two)${NOR}" - echo -e "${YEL}Note: It's safe to answer N, then re-run script again later.${NOR}" - showrun $CLEANUP_CMD # prompts for Yes/No - cleanup -} - -get_env_vars() { - # Deal with both YAML and embedded shell-like substitutions in values - # if substitution fails, fall back to printing naked env. var as-is. - python3 -c ' -import sys,yaml,re -env=yaml.load(open(".cirrus.yml"), Loader=yaml.SafeLoader)["env"] -dollar_env_var=re.compile(r"\$(\w+)") -dollarcurly_env_var=re.compile(r"\$\{(\w+)\}") -class ReIterKey(dict): - def __missing__(self, key): - # Cirrus-CI provides some runtime-only env. vars. Avoid - # breaking this hack-script if/when any are present in YAML - return "${0}".format(key) -rep=r"{\1}" # Convert env vars markup to -> str.format_map(re_iter_key) markup -out=ReIterKey() -for k,v in env.items(): - if "ENCRYPTED" not in str(v) and bool(v): - out[k]=dollar_env_var.sub(rep, dollarcurly_env_var.sub(rep, str(v))) -for k,v in out.items(): - sys.stdout.write("{0}=\"{1}\"\n".format(k, str(v).format_map(out))) - ' -} - -image_hints() { - get_env_vars | fgrep '_CACHE_IMAGE_NAME' | awk -F "=" '{print $2}' } -unset VM_IMAGE_NAME -unset VMNAME -unset CREATE_CMD -unset SSH_CMD -unset CLEANUP_CMD -declare -xa ENVS -parse_args(){ - local arg - echo -e "$USAGE_WARNING" - - if [[ "$USER" =~ "root" ]] - then - show_usage "This script must be run as a regular user." - fi - - [[ "$#" -eq 1 ]] || \ - show_usage "Must specify a VM Image name to use, and the test flavor." - - VM_IMAGE_NAME="$1" - - # Word-splitting is desirable in this case. - # Values are used literally (with '=') as args to future `env` command. - # get_env_vars() will take care of properly quoting it's output. - # shellcheck disable=SC2207,SC2191 - ENVS=( - $(get_env_vars) - VM_IMAGE_NAME="$VM_IMAGE_NAME" - UPSTREAM_REMOTE="upstream" - ) - - VMNAME="${VMNAME:-${USER}-${VM_IMAGE_NAME}}" - - CREATE_CMD="$PGCLOUD compute instances create --zone=$ZONE --image=${VM_IMAGE_NAME} --custom-cpu=$CPUS --custom-memory=$MEMORY --boot-disk-size=$DISK --labels=in-use-by=$USER $VMNAME" - - SSH_CMD="$PGCLOUD compute ssh root@$VMNAME" - - CLEANUP_CMD="$PGCLOUD compute instances delete --zone $ZONE --delete-disks=all $VMNAME" -} - -# Returns true if user has run an 'init' and has a valid token for -# the specific project-id and named-configuration arguments in $PGCLOUD. -function has_valid_credentials() { - if $PGCLOUD info |& grep -Eq 'Account:.*None'; then - return 1 - fi - - # It's possible for 'gcloud info' to list expired credentials, - # e.g. 'ERROR: ... invalid grant: Bad Request' - if $PGCLOUD auth print-access-token |& grep -q 'ERROR'; then - return 1 - fi - - return 0 -} - -##### main - -[[ "${LIBPODROOT%%${LIBPODROOT##$HOME}}" == "$HOME" ]] || \ - show_usage "Repo clone must be sub-dir of $HOME" - -cd "$LIBPODROOT" - -parse_args "$@" -mkdir -p $TMPDIR/.ssh -mkdir -p {$HOME,$TMPDIR}/.config/gcloud/ssh -chmod 700 {$HOME,$TMPDIR}/.config/gcloud/ssh $TMPDIR/.ssh - -echo -e "\n${YEL}Pulling gcloud image...${NOR}" -podman pull $GCLOUD_IMAGE - -if ! has_valid_credentials -then - echo -e "\n${YEL}WARNING: Can't find gcloud configuration for libpod, running init.${NOR}" - echo -e " ${RED}Please choose \"#1: Re-initialize\" and \"login\" if asked.${NOR}" - showrun $PGCLOUD init --project=$PROJECT --console-only --skip-diagnostics - - # Verify it worked (account name == someone@example.com) - $PGCLOUD info > $TMPDIR/gcloud-info-after-init - if egrep -q "Account:.*None" $TMPDIR/gcloud-info-after-init - then - echo -e "${RED}ERROR: Could not initialize libpod configuration in gcloud.${NOR}" - exit 5 - fi - - # If this is the only config, make it the default to avoid - # persistent warnings from gcloud about there being no default. - [[ -r "$HOME/.config/gcloud/configurations/config_default" ]] || \ - ln "$HOME/.config/gcloud/configurations/config_libpod" \ - "$HOME/.config/gcloud/configurations/config_default" +# get_ci_vm APIv1 container entrypoint calls into this script +# to obtain required repo. specific configuration options. +if [[ "$1" == "--config" ]]; then + in_get_ci_vm "$1" + cat <<EOF +DESTDIR="/var/tmp/go/src/github.com/containers/podman" +UPSTREAM_REPO="https://github.com/containers/podman.git" +CI_ENVFILE="/etc/ci_environment" +GCLOUD_PROJECT="libpod-218412" +GCLOUD_IMGPROJECT="libpod-218412" +GCLOUD_CFG="libpod" +GCLOUD_ZONE="${GCLOUD_ZONE:-us-central1-a}" +GCLOUD_CPUS="2" +GCLOUD_MEMORY="4Gb" +GCLOUD_DISK="200" +EOF +elif [[ "$1" == "--setup" ]]; then + in_get_ci_vm "$1" + # get_ci_vm container entrypoint calls us with this option on the + # Cirrus-CI environment instance, to perform repo.-specific setup. + cd $REPO_DIRPATH + echo "+ Loading ./contrib/cirrus/lib.sh" > /dev/stderr + source ./contrib/cirrus/lib.sh + echo "+ Mimicking .cirrus.yml clone_script and build_task" > /dev/stderr + make install.tools + make vendor + make podman + make podman-remote + echo "+ Running environment setup" > /dev/stderr + ./contrib/cirrus/setup_environment.sh +else + # Create and access VM for specified Cirrus-CI task + mkdir -p $HOME/.config/gcloud/ssh + podman run -it --rm \ + --tz=local \ + -e NAME="$USER" \ + -e SRCDIR=/src \ + -e GCLOUD_ZONE="$GCLOUD_ZONE" \ + -e DEBUG="${DEBUG:-0}" \ + -v $REPO_DIRPATH:/src:O \ + -v $HOME/.config/gcloud:/root/.config/gcloud:z \ + -v $HOME/.config/gcloud/ssh:/root/.ssh:z \ + quay.io/libpod/get_ci_vm:latest "$@" fi - -trap delvm EXIT # Allow deleting VM if CTRL-C during create -echo -e "\n${YEL}Trying to creating a VM named $VMNAME${NOR}\n${YEL}in GCE region/zone $ZONE${NOR}" -echo -e "For faster terminal access, export ZONE='<something-closer>'" -echo -e 'Zone-list at: https://cloud.google.com/compute/docs/regions-zones/\n' -if showrun $CREATE_CMD; then # Freshly created VM needs initial setup - - echo -e "\n${YEL}Waiting up to 30s for ssh port to open${NOR}" - ATTEMPTS=10 - trap "exit 1" INT - while ((ATTEMPTS)) && ! $SSH_CMD --command "true"; do - let "ATTEMPTS--" - echo -e "${RED}Nope, not yet.${NOR}" - sleep 3s - done - trap - INT - if ! ((ATTEMPTS)); then - echo -e "\n${RED}Failed${NOR}" - exit 7 - fi - echo -e "${YEL}Got it. Cloning upstream repository as a starting point.${NOR}" - - showrun $SSH_CMD -- "mkdir -p $GOSRC" - showrun $SSH_CMD -- "git clone --progress $GIT_REPO $GOSRC" - - if [[ -x "$HOME/$GCLOUD_CFGDIR/$HOOK_FILENAME" ]]; then - echo -e "\n${YEL}Copying hook to VM and executing (ignoring errors).${NOR}" - $PGCLOUD compute scp "/root/$GCLOUD_CFGDIR/$HOOK_FILENAME" root@$VMNAME:. - if ! showrun $SSH_CMD -- "cd $GOSRC && bash /root/$HOOK_FILENAME $(git branch --show-current) $(git rev-parse HEAD)"; then - echo "-e ${RED}Hook exited: $?${NOR}" - fi - fi -fi - -echo -e "\n${YEL}Generating connection script for $VMNAME.${NOR}" -echo -e "Note: Script can be re-used in another terminal if needed." -echo -e "${RED}(option to delete VM presented upon exiting).${NOR}" -# TODO: This is fairly fragile, specifically the quoting for the remote command. -echo '#!/bin/bash' > $TMPDIR/ssh -echo "$SSH_CMD -- -t 'cd $GOSRC && exec env ${ENVS[*]} bash -il'" >> $TMPDIR/ssh -chmod +x $TMPDIR/ssh - -showrun $TMPDIR/ssh diff --git a/hack/swagger-check b/hack/swagger-check index f2e30cc33..646cbcb84 100755 --- a/hack/swagger-check +++ b/hack/swagger-check @@ -59,11 +59,6 @@ says 'libpod'. OPTIONS: - --pedantic Compare operation names (the last part of swagger comment). - There are far too many of these inconsistencies to allow us - to enable this by default, but it still might be a useful - check in some circumstances. - -v, --verbose show verbose progress indicators -n, --dry-run make no actual changes @@ -75,7 +70,6 @@ END_USAGE } # Command-line options. Note that this operates directly on @ARGV ! -our $pedantic; our $debug = 0; our $force = 0; our $verbose = 0; @@ -83,8 +77,6 @@ our $NOT = ''; # print "blahing the blah$NOT\n" if $debug sub handle_opts { use Getopt::Long; GetOptions( - 'pedantic' => \$pedantic, - 'debug!' => \$debug, 'dry-run|n!' => sub { $NOT = ' [NOT]' }, 'force' => \$force, @@ -225,26 +217,14 @@ sub handle_handle { my $tag = ($endpoint =~ /(libpod)/ ? $1 : 'compat'); # - # Determine the OPERATION. *** NOTE: This is mostly useless! *** - # In an ideal world the swagger comment would match actual function call; - # in reality there are over thirty mismatches. Use --pedantic to see. + # Determine the OPERATION. Done in a helper function because there + # are a lot of complicated special cases. # - my $operation = ''; - if ($line =~ /(generic|handlers|compat)\.(\w+)/) { - $operation = lcfirst $2; - if ($endpoint =~ m!/libpod/! && $operation !~ /^libpod/) { - $operation = 'libpod' . ucfirst $operation; - } - } - elsif ($line =~ /(libpod)\.(\w+)/) { - $operation = "$1$2"; - } + my $operation = operation_name($method, $endpoint); # Special case: the following endpoints all get a custom tag if ($endpoint =~ m!/(pods|manifests)/!) { $tag = $1; - $operation =~ s/^libpod//; - $operation = lcfirst $operation; } # Special case: anything related to 'events' gets a system tag @@ -264,13 +244,6 @@ sub handle_handle { my $actual = $actual[0]; - # By default, don't compare the operation: there are far too many - # mismatches here. - if (! $pedantic) { - $actual =~ s/\s+\S+\s*$//; - $expect =~ s/\s+\S+\s*$//; - } - # (Ignore whitespace discrepancies) (my $a_trimmed = $actual) =~ s/\s+/ /g; @@ -309,4 +282,72 @@ sub handle_handle { return; } + +#################### +# operation_name # Given a method + endpoint, return the swagger operation +#################### +sub operation_name { + my ($method, $endpoint) = @_; + + # /libpod/foo/bar -> (libpod, foo, bar) + my @endpoints = grep { /\S/ } split '/', $endpoint; + + # /libpod endpoints -> add 'Libpod' to end, e.g. PodStatsLibpod + my $Libpod = ''; + my $main = shift(@endpoints); + if ($main eq 'libpod') { + $Libpod = ucfirst($main); + $main = shift(@endpoints); + } + $main =~ s/s$//; # e.g. Volumes -> Volume + + # Next path component is an optional action: + # GET /containers/json -> ContainerList + # DELETE /libpod/containers/{name} -> ContainerDelete + # GET /libpod/containers/{name}/logs -> ContainerLogsLibpod + my $action = shift(@endpoints) || 'list'; + $action = 'list' if $action eq 'json'; + $action = 'delete' if $method eq 'DELETE'; + + # Anything with {id}, {name}, {name:..} may have a following component + if ($action =~ m!\{.*\}!) { + $action = shift(@endpoints) || 'inspect'; + $action = 'inspect' if $action eq 'json'; + } + + # All sorts of special cases + if ($action eq 'df') { + $action = 'dataUsage'; + } + # Grrrrrr, this one is annoying: some operations get an extra 'All' + elsif ($action =~ /^(delete|get|stats)$/ && $endpoint !~ /\{/) { + $action .= "All"; + $main .= 's' if $main eq 'container'; + } + # No real way to used MixedCase in an endpoint, so we have to hack it here + elsif ($action eq 'showmounted') { + $action = 'showMounted'; + } + # Ping is a special endpoint, and even if /libpod/_ping, no 'Libpod' + elsif ($main eq '_ping') { + $main = 'system'; + $action = 'ping'; + $Libpod = ''; + } + # Top-level compat endpoints + elsif ($main =~ /^(build|commit)$/) { + $main = 'image'; + $action = $1; + } + # Top-level system endpoints + elsif ($main =~ /^(auth|event|info|version)$/) { + $main = 'system'; + $action = $1; + $action .= 's' if $action eq 'event'; + } + + return "\u${main}\u${action}$Libpod"; +} + + 1; diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index d4994334f..f63876c14 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -919,7 +919,7 @@ func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error return err } if len(deps) != 0 { - return errors.Wrapf(define.ErrCtrExists, "container %s is a dependency of the following containers: %s", ctr.ID(), strings.Join(deps, ", ")) + return errors.Wrapf(define.ErrDepExists, "container %s is a dependency of the following containers: %s", ctr.ID(), strings.Join(deps, ", ")) } if err := ctrBucket.DeleteBucket(ctrID); err != nil { diff --git a/libpod/container_exec.go b/libpod/container_exec.go index bb43287d9..8d8ed14aa 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -696,6 +696,24 @@ func (c *Container) ExecResize(sessionID string, newSize define.TerminalSize) er return errors.Wrapf(define.ErrExecSessionStateInvalid, "cannot resize container %s exec session %s as it is not running", c.ID(), session.ID()) } + // The exec session may have exited since we last updated. + // Needed to prevent race conditions around short-running exec sessions. + running, err := c.ociRuntime.ExecUpdateStatus(c, session.ID()) + if err != nil { + return err + } + if !running { + session.State = define.ExecStateStopped + + if err := c.save(); err != nil { + logrus.Errorf("Error saving state of container %s: %v", c.ID(), err) + } + + return errors.Wrapf(define.ErrExecSessionStateInvalid, "cannot resize container %s exec session %s as it has stopped", c.ID(), session.ID()) + } + + // Make sure the exec session is still running. + return c.ociRuntime.ExecAttachResize(c, sessionID, newSize) } @@ -720,8 +738,13 @@ func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resi logrus.Debugf("Sending resize events to exec session %s", sessionID) for resizeRequest := range resize { if err := c.ExecResize(sessionID, resizeRequest); err != nil { - // Assume the exec session went down. - logrus.Warnf("Error resizing exec session %s: %v", sessionID, err) + if errors.Cause(err) == define.ErrExecSessionStateInvalid { + // The exec session stopped + // before we could resize. + logrus.Infof("Missed resize on exec session %s, already stopped", sessionID) + } else { + logrus.Warnf("Error resizing exec session %s: %v", sessionID, err) + } return } } diff --git a/libpod/container_validate.go b/libpod/container_validate.go index 245121a91..aae96ae85 100644 --- a/libpod/container_validate.go +++ b/libpod/container_validate.go @@ -126,5 +126,11 @@ func (c *Container) validate() error { } } + // If User in the OCI spec is set, require that c.config.User is set for + // security reasons (a lot of our code relies on c.config.User). + if c.config.User == "" && (c.config.Spec.Process.User.UID != 0 || c.config.Spec.Process.User.GID != 0) { + return errors.Wrapf(define.ErrInvalidArg, "please set User explicitly via WithUser() instead of in OCI spec directly") + } + return nil } diff --git a/libpod/define/errors.go b/libpod/define/errors.go index 2e85454b2..e19ac6a27 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -31,6 +31,10 @@ var ( // not exist. ErrNoSuchExecSession = errors.New("no such exec session") + // ErrDepExists indicates that the current object has dependencies and + // cannot be removed before them. + ErrDepExists = errors.New("dependency exists") + // ErrNoAliases indicates that the container does not have any network // aliases. ErrNoAliases = errors.New("no aliases for container") diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 3875878ed..df45f8e73 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -391,7 +391,7 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error { deps, ok := s.ctrDepends[ctr.ID()] if ok && len(deps) != 0 { depsStr := strings.Join(deps, ", ") - return errors.Wrapf(define.ErrCtrExists, "the following containers depend on container %s: %s", ctr.ID(), depsStr) + return errors.Wrapf(define.ErrDepExists, "the following containers depend on container %s: %s", ctr.ID(), depsStr) } // Ensure we don't have active exec sessions @@ -1497,7 +1497,7 @@ func (s *InMemoryState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { deps, ok := s.ctrDepends[ctr.ID()] if ok && len(deps) != 0 { depsStr := strings.Join(deps, ", ") - return errors.Wrapf(define.ErrCtrExists, "the following containers depend on container %s: %s", ctr.ID(), depsStr) + return errors.Wrapf(define.ErrDepExists, "the following containers depend on container %s: %s", ctr.ID(), depsStr) } // Ensure we don't have active exec sessions diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 157c85431..3c4014c73 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -411,6 +411,16 @@ func (r *Runtime) getRootlessCNINetNs(new bool) (*rootlessCNI, error) { } } + // The CNI plugins need access to iptables in $PATH. As it turns out debian doesn't put + // /usr/sbin in $PATH for rootless users. This will break rootless cni completely. + // We might break existing users and we cannot expect everyone to change their $PATH so + // lets add /usr/sbin to $PATH ourselves. + path = os.Getenv("PATH") + if !strings.Contains(path, "/usr/sbin") { + path = path + ":/usr/sbin" + os.Setenv("PATH", path) + } + rootlessCNINS = &rootlessCNI{ ns: ns, dir: cniDir, diff --git a/libpod/oci_conmon_exec_linux.go b/libpod/oci_conmon_exec_linux.go index 173edba2b..b43316951 100644 --- a/libpod/oci_conmon_exec_linux.go +++ b/libpod/oci_conmon_exec_linux.go @@ -2,18 +2,23 @@ package libpod import ( "fmt" + "io/ioutil" "net/http" "os" "os/exec" "path/filepath" + "strings" "syscall" "time" + "github.com/containers/common/pkg/capabilities" "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/errorhandling" + "github.com/containers/podman/v3/pkg/lookup" "github.com/containers/podman/v3/pkg/util" "github.com/containers/podman/v3/utils" + spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" @@ -654,3 +659,122 @@ func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.Resp } } } + +// prepareProcessExec returns the path of the process.json used in runc exec -p +// caller is responsible to close the returned *os.File if needed. +func prepareProcessExec(c *Container, options *ExecOptions, env []string, sessionID string) (*os.File, error) { + f, err := ioutil.TempFile(c.execBundlePath(sessionID), "exec-process-") + if err != nil { + return nil, err + } + pspec := new(spec.Process) + if err := JSONDeepCopy(c.config.Spec.Process, pspec); err != nil { + return nil, err + } + pspec.SelinuxLabel = c.config.ProcessLabel + pspec.Args = options.Cmd + + // We need to default this to false else it will inherit terminal as true + // from the container. + pspec.Terminal = false + if options.Terminal { + pspec.Terminal = true + } + if len(env) > 0 { + pspec.Env = append(pspec.Env, env...) + } + + if options.Cwd != "" { + pspec.Cwd = options.Cwd + } + + var addGroups []string + var sgids []uint32 + + // if the user is empty, we should inherit the user that the container is currently running with + user := options.User + if user == "" { + logrus.Debugf("Set user to %s", c.config.User) + user = c.config.User + addGroups = c.config.Groups + } + + overrides := c.getUserOverrides() + execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, overrides) + if err != nil { + return nil, err + } + + if len(addGroups) > 0 { + sgids, err = lookup.GetContainerGroups(addGroups, c.state.Mountpoint, overrides) + if err != nil { + return nil, errors.Wrapf(err, "error looking up supplemental groups for container %s exec session %s", c.ID(), sessionID) + } + } + + // If user was set, look it up in the container to get a UID to use on + // the host + if user != "" || len(sgids) > 0 { + if user != "" { + for _, sgid := range execUser.Sgids { + sgids = append(sgids, uint32(sgid)) + } + } + processUser := spec.User{ + UID: uint32(execUser.Uid), + GID: uint32(execUser.Gid), + AdditionalGids: sgids, + } + + pspec.User = processUser + } + + ctrSpec, err := c.specFromState() + if err != nil { + return nil, err + } + + allCaps, err := capabilities.BoundingSet() + if err != nil { + return nil, err + } + if options.Privileged { + pspec.Capabilities.Bounding = allCaps + } else { + pspec.Capabilities.Bounding = ctrSpec.Process.Capabilities.Bounding + } + if execUser.Uid == 0 { + pspec.Capabilities.Effective = pspec.Capabilities.Bounding + pspec.Capabilities.Inheritable = pspec.Capabilities.Bounding + pspec.Capabilities.Permitted = pspec.Capabilities.Bounding + pspec.Capabilities.Ambient = pspec.Capabilities.Bounding + } else { + if user == c.config.User { + pspec.Capabilities.Effective = ctrSpec.Process.Capabilities.Effective + pspec.Capabilities.Inheritable = ctrSpec.Process.Capabilities.Effective + pspec.Capabilities.Permitted = ctrSpec.Process.Capabilities.Effective + pspec.Capabilities.Ambient = ctrSpec.Process.Capabilities.Effective + } + } + + hasHomeSet := false + for _, s := range pspec.Env { + if strings.HasPrefix(s, "HOME=") { + hasHomeSet = true + break + } + } + if !hasHomeSet { + pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home)) + } + + processJSON, err := json.Marshal(pspec) + if err != nil { + return nil, err + } + + if err := ioutil.WriteFile(f.Name(), processJSON, 0644); err != nil { + return nil, err + } + return f, nil +} diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index 1c7089e5d..f26ca67ce 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -22,7 +22,6 @@ import ( "text/template" "time" - "github.com/containers/common/pkg/capabilities" "github.com/containers/common/pkg/config" conmonConfig "github.com/containers/conmon/runner/config" "github.com/containers/podman/v3/libpod/define" @@ -30,7 +29,6 @@ import ( "github.com/containers/podman/v3/pkg/cgroups" "github.com/containers/podman/v3/pkg/checkpoint/crutils" "github.com/containers/podman/v3/pkg/errorhandling" - "github.com/containers/podman/v3/pkg/lookup" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/pkg/util" "github.com/containers/podman/v3/utils" @@ -1195,124 +1193,6 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co return nil } -// prepareProcessExec returns the path of the process.json used in runc exec -p -// caller is responsible to close the returned *os.File if needed. -func prepareProcessExec(c *Container, options *ExecOptions, env []string, sessionID string) (*os.File, error) { - f, err := ioutil.TempFile(c.execBundlePath(sessionID), "exec-process-") - if err != nil { - return nil, err - } - pspec := new(spec.Process) - if err := JSONDeepCopy(c.config.Spec.Process, pspec); err != nil { - return nil, err - } - pspec.SelinuxLabel = c.config.ProcessLabel - pspec.Args = options.Cmd - - // We need to default this to false else it will inherit terminal as true - // from the container. - pspec.Terminal = false - if options.Terminal { - pspec.Terminal = true - } - if len(env) > 0 { - pspec.Env = append(pspec.Env, env...) - } - - if options.Cwd != "" { - pspec.Cwd = options.Cwd - } - - var addGroups []string - var sgids []uint32 - - // if the user is empty, we should inherit the user that the container is currently running with - user := options.User - if user == "" { - user = c.config.User - addGroups = c.config.Groups - } - - overrides := c.getUserOverrides() - execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, overrides) - if err != nil { - return nil, err - } - - if len(addGroups) > 0 { - sgids, err = lookup.GetContainerGroups(addGroups, c.state.Mountpoint, overrides) - if err != nil { - return nil, errors.Wrapf(err, "error looking up supplemental groups for container %s exec session %s", c.ID(), sessionID) - } - } - - // If user was set, look it up in the container to get a UID to use on - // the host - if user != "" || len(sgids) > 0 { - if user != "" { - for _, sgid := range execUser.Sgids { - sgids = append(sgids, uint32(sgid)) - } - } - processUser := spec.User{ - UID: uint32(execUser.Uid), - GID: uint32(execUser.Gid), - AdditionalGids: sgids, - } - - pspec.User = processUser - } - - ctrSpec, err := c.specFromState() - if err != nil { - return nil, err - } - - allCaps, err := capabilities.BoundingSet() - if err != nil { - return nil, err - } - if options.Privileged { - pspec.Capabilities.Bounding = allCaps - } else { - pspec.Capabilities.Bounding = ctrSpec.Process.Capabilities.Bounding - } - if execUser.Uid == 0 { - pspec.Capabilities.Effective = pspec.Capabilities.Bounding - pspec.Capabilities.Inheritable = pspec.Capabilities.Bounding - pspec.Capabilities.Permitted = pspec.Capabilities.Bounding - pspec.Capabilities.Ambient = pspec.Capabilities.Bounding - } else { - if user == c.config.User { - pspec.Capabilities.Effective = ctrSpec.Process.Capabilities.Effective - pspec.Capabilities.Inheritable = ctrSpec.Process.Capabilities.Effective - pspec.Capabilities.Permitted = ctrSpec.Process.Capabilities.Effective - pspec.Capabilities.Ambient = ctrSpec.Process.Capabilities.Effective - } - } - - hasHomeSet := false - for _, s := range pspec.Env { - if strings.HasPrefix(s, "HOME=") { - hasHomeSet = true - break - } - } - if !hasHomeSet { - pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home)) - } - - processJSON, err := json.Marshal(pspec) - if err != nil { - return nil, err - } - - if err := ioutil.WriteFile(f.Name(), processJSON, 0644); err != nil { - return nil, err - } - return f, nil -} - // configureConmonEnv gets the environment values to add to conmon's exec struct // TODO this may want to be less hardcoded/more configurable in the future func (r *ConmonOCIRuntime) configureConmonEnv(ctr *Container, runtimeDir string) ([]string, []*os.File) { diff --git a/nix/default-arm64.nix b/nix/default-arm64.nix new file mode 100644 index 000000000..3e374c1bd --- /dev/null +++ b/nix/default-arm64.nix @@ -0,0 +1,69 @@ +let + pkgs = (import ./nixpkgs.nix { + crossSystem = { + config = "aarch64-unknown-linux-gnu"; + }; + config = { + packageOverrides = pkg: { + gpgme = (static pkg.gpgme); + libassuan = (static pkg.libassuan); + libgpgerror = (static pkg.libgpgerror); + libseccomp = (static pkg.libseccomp); + glib = (static pkg.glib).overrideAttrs (x: { + outputs = [ "bin" "out" "dev" ]; + mesonFlags = [ + "-Ddefault_library=static" + "-Ddevbindir=${placeholder ''dev''}/bin" + "-Dgtk_doc=false" + "-Dnls=disabled" + ]; + postInstall = '' + moveToOutput "share/glib-2.0" "$dev" + substituteInPlace "$dev/bin/gdbus-codegen" --replace "$out" "$dev" + sed -i "$dev/bin/glib-gettextize" -e "s|^gettext_dir=.*|gettext_dir=$dev/share/glib-2.0/gettext|" + sed '1i#line 1 "${x.pname}-${x.version}/include/glib-2.0/gobject/gobjectnotifyqueue.c"' \ + -i "$dev"/include/glib-2.0/gobject/gobjectnotifyqueue.c + ''; + }); + }; + }; + }); + + static = pkg: pkg.overrideAttrs (x: { + doCheck = false; + configureFlags = (x.configureFlags or [ ]) ++ [ + "--without-shared" + "--disable-shared" + ]; + dontDisableStatic = true; + enableSharedExecutables = false; + enableStatic = true; + }); + + self = with pkgs; buildGoModule rec { + name = "podman"; + src = ./..; + vendorSha256 = null; + doCheck = false; + enableParallelBuilding = true; + outputs = [ "out" ]; + nativeBuildInputs = [ bash gitMinimal go-md2man installShellFiles makeWrapper pkg-config which ]; + buildInputs = [ glibc glibc.static gpgme libassuan libgpgerror libseccomp libapparmor libselinux ]; + prePatch = '' + export CFLAGS='-static -pthread' + export LDFLAGS='-s -w -static-libgcc -static' + export EXTRA_LDFLAGS='-s -w -linkmode external -extldflags "-static -lm"' + export BUILDTAGS='static netgo osusergo exclude_graphdriver_btrfs exclude_graphdriver_devicemapper seccomp apparmor selinux' + ''; + buildPhase = '' + patchShebangs . + make bin/podman + make bin/podman-remote + ''; + installPhase = '' + install -Dm755 bin/podman $out/bin/podman + install -Dm755 bin/podman-remote $out/bin/podman-remote + ''; + }; +in +self diff --git a/nix/default.nix b/nix/default.nix index 7745d8b50..aae520c9e 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -7,7 +7,7 @@ let libassuan = (static pkg.libassuan); libgpgerror = (static pkg.libgpgerror); libseccomp = (static pkg.libseccomp); - glib = (static pkg.glib).overrideAttrs(x: { + glib = (static pkg.glib).overrideAttrs (x: { outputs = [ "bin" "out" "dev" ]; mesonFlags = [ "-Ddefault_library=static" @@ -15,14 +15,21 @@ let "-Dgtk_doc=false" "-Dnls=disabled" ]; + postInstall = '' + moveToOutput "share/glib-2.0" "$dev" + substituteInPlace "$dev/bin/gdbus-codegen" --replace "$out" "$dev" + sed -i "$dev/bin/glib-gettextize" -e "s|^gettext_dir=.*|gettext_dir=$dev/share/glib-2.0/gettext|" + sed '1i#line 1 "${x.pname}-${x.version}/include/glib-2.0/gobject/gobjectnotifyqueue.c"' \ + -i "$dev"/include/glib-2.0/gobject/gobjectnotifyqueue.c + ''; }); }; }; }); - static = pkg: pkg.overrideAttrs(x: { + static = pkg: pkg.overrideAttrs (x: { doCheck = false; - configureFlags = (x.configureFlags or []) ++ [ + configureFlags = (x.configureFlags or [ ]) ++ [ "--without-shared" "--disable-shared" ]; @@ -56,4 +63,5 @@ let install -Dm755 bin/podman-remote $out/bin/podman-remote ''; }; -in self +in +self diff --git a/nix/nixpkgs.json b/nix/nixpkgs.json index cc8daf55c..8d2f76a28 100644 --- a/nix/nixpkgs.json +++ b/nix/nixpkgs.json @@ -1,9 +1,9 @@ { "url": "https://github.com/nixos/nixpkgs", - "rev": "f38b9b258f3f4db5ecf7dd27a7d5b48f23202843", - "date": "2021-03-07T14:22:16+01:00", - "path": "/nix/store/df3v1b2qfsbnsd6fwaw4787qdy5rcxkc-nixpkgs", - "sha256": "1dbi7rjyfkv3rw6zqwbc6jknbdgyv16cd8zgcpq5gdj0mwnp9b13", + "rev": "42a03e4728fc05cb9f123057670e41967f628360", + "date": "2021-04-02T23:08:32+02:00", + "path": "/nix/store/d1vqa0kpa69zzcaj5kqgkmrxr3s7vli1-nixpkgs", + "sha256": "0wrn5nayxckj11z2qlvsya2lzssbccbk50llxmgdm0qb5y14shfk", "fetchSubmodules": false, "deepClone": false, "leaveDotGit": false diff --git a/pkg/api/handlers/compat/containers_start.go b/pkg/api/handlers/compat/containers_start.go index 391aa752d..f1ed1b2b8 100644 --- a/pkg/api/handlers/compat/containers_start.go +++ b/pkg/api/handlers/compat/containers_start.go @@ -42,7 +42,7 @@ func StartContainer(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusNotModified, nil) return } - if err := con.Start(r.Context(), len(con.PodID()) > 0); err != nil { + if err := con.Start(r.Context(), true); err != nil { utils.InternalServerError(w, err) return } diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index ab92434b1..700881926 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -15,6 +15,7 @@ import ( "github.com/containers/buildah" buildahDefine "github.com/containers/buildah/define" + "github.com/containers/buildah/pkg/parse" "github.com/containers/buildah/util" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" @@ -445,6 +446,17 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { }, } + if len(query.Platform) > 0 { + variant := "" + buildOptions.OS, buildOptions.Architecture, variant, err = parse.Platform(query.Platform) + if err != nil { + utils.BadRequest(w, "platform", query.Platform, err) + return + } + buildOptions.SystemContext.OSChoice = buildOptions.OS + buildOptions.SystemContext.ArchitectureChoice = buildOptions.Architecture + buildOptions.SystemContext.VariantChoice = variant + } if _, found := r.URL.Query()["timestamp"]; found { ts := time.Unix(query.Timestamp, 0) buildOptions.Timestamp = &ts diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go index f63e35bf1..0d22c32f8 100644 --- a/pkg/bindings/containers/types.go +++ b/pkg/bindings/containers/types.go @@ -154,6 +154,7 @@ type RestartOptions struct { // StartOptions are optional options for starting containers type StartOptions struct { DetachKeys *string + Recursive *bool } //go:generate go run ../generator/generator.go StatsOptions diff --git a/pkg/bindings/containers/types_start_options.go b/pkg/bindings/containers/types_start_options.go index f8ba29623..d419c755c 100644 --- a/pkg/bindings/containers/types_start_options.go +++ b/pkg/bindings/containers/types_start_options.go @@ -35,3 +35,19 @@ func (o *StartOptions) GetDetachKeys() string { } return *o.DetachKeys } + +// WithRecursive +func (o *StartOptions) WithRecursive(value bool) *StartOptions { + v := &value + o.Recursive = v + return o +} + +// GetRecursive +func (o *StartOptions) GetRecursive() bool { + var recursive bool + if o.Recursive == nil { + return recursive + } + return *o.Recursive +} diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index c47a16551..34d6cee05 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -12,6 +12,7 @@ import ( "os" "path/filepath" "regexp" + "runtime" "strconv" "strings" @@ -190,6 +191,10 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO platform = "linux" } platform += "/" + options.Architecture + } else { + if len(platform) > 0 { + platform += "/" + runtime.GOARCH + } } if len(platform) > 0 { params.Set("platform", platform) diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 24261e5ed..6f8845f10 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -585,7 +585,7 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrID string, } // If the container is in a pod, also set to recursively start dependencies - err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false, ctr.PodID() != "") + err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false) if err != nil && errors.Cause(err) != define.ErrDetach { return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) } @@ -708,7 +708,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri ctrRunning := ctrState == define.ContainerStateRunning if options.Attach { - err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, !ctrRunning, ctr.PodID() != "") + err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, !ctrRunning) if errors.Cause(err) == define.ErrDetach { // User manually detached // Exit cleanly immediately @@ -784,7 +784,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri RawInput: rawInput, ExitCode: 125, } - if err := ctr.Start(ctx, ctr.PodID() != ""); err != nil { + if err := ctr.Start(ctx, true); err != nil { // if lastError != nil { // fmt.Fprintln(os.Stderr, lastError) // } @@ -845,10 +845,6 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta } } - var joinPod bool - if len(ctr.PodID()) > 0 { - joinPod = true - } report := entities.ContainerRunReport{Id: ctr.ID()} if logrus.GetLevel() == logrus.DebugLevel { @@ -859,7 +855,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta } if opts.Detach { // if the container was created as part of a pod, also start its dependencies, if any. - if err := ctr.Start(ctx, joinPod); err != nil { + if err := ctr.Start(ctx, true); err != nil { // This means the command did not exist report.ExitCode = define.ExitCode(err) return &report, err @@ -869,7 +865,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta } // if the container was created as part of a pod, also start its dependencies, if any. - if err := terminal.StartAttachCtr(ctx, ctr, opts.OutputStream, opts.ErrorStream, opts.InputStream, opts.DetachKeys, opts.SigProxy, true, joinPod); err != nil { + if err := terminal.StartAttachCtr(ctx, ctr, opts.OutputStream, opts.ErrorStream, opts.InputStream, opts.DetachKeys, opts.SigProxy, true); err != nil { // We've manually detached from the container // Do not perform cleanup, or wait for container exit code // Just exit immediately diff --git a/pkg/domain/infra/abi/terminal/terminal_linux.go b/pkg/domain/infra/abi/terminal/terminal_linux.go index 7a0c2907c..ab71f8f6f 100644 --- a/pkg/domain/infra/abi/terminal/terminal_linux.go +++ b/pkg/domain/infra/abi/terminal/terminal_linux.go @@ -39,7 +39,7 @@ func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, execConfig *libpo // StartAttachCtr starts and (if required) attaches to a container // if you change the signature of this function from os.File to io.Writer, it will trigger a downstream // error. we may need to just lint disable this one. -func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error { //nolint-interfacer +func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool) error { //nolint-interfacer resize := make(chan define.TerminalSize) haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) @@ -88,7 +88,7 @@ func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, return ctr.Attach(streams, detachKeys, resize) } - attachChan, err := ctr.StartAndAttach(ctx, streams, detachKeys, resize, recursive) + attachChan, err := ctr.StartAndAttach(ctx, streams, detachKeys, resize, true) if err != nil { return err } diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index a0f65f11f..4545d266b 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -629,7 +629,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta if opts.Detach { // Detach and return early - err := containers.Start(ic.ClientCtx, con.ID, nil) + err := containers.Start(ic.ClientCtx, con.ID, new(containers.StartOptions).WithRecursive(true)) if err != nil { report.ExitCode = define.ExitCode(err) } diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 1d724ffb0..ef9975021 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -364,6 +364,17 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. if len(s.Secrets) != 0 { options = append(options, libpod.WithSecrets(s.Secrets)) } + if len(s.DependencyContainers) > 0 { + deps := make([]*libpod.Container, 0, len(s.DependencyContainers)) + for _, ctr := range s.DependencyContainers { + depCtr, err := rt.LookupContainer(ctr) + if err != nil { + return nil, errors.Wrapf(err, "%q is not a valid container, cannot be used as a dependency", ctr) + } + deps = append(deps, depCtr) + } + options = append(options, libpod.WithDependencyCtrs(deps)) + } return options, nil } diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 845dfdad7..b52e8d100 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -157,6 +157,16 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. case specgen.KeepID: if rootless.IsRootless() { toReturn = append(toReturn, libpod.WithAddCurrentUserPasswdEntry()) + + // If user is not overridden, set user in the container + // to user running Podman. + if s.User == "" { + _, uid, gid, err := util.GetKeepIDMapping() + if err != nil { + return nil, err + } + toReturn = append(toReturn, libpod.WithUser(fmt.Sprintf("%d:%d", uid, gid))) + } } else { // keep-id as root doesn't need a user namespace s.UserNS.NSMode = specgen.Host diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index c10dc5ef5..28111f96d 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -160,10 +160,17 @@ type ContainerBasicConfig struct { // to 0, 1, 2) that will be passed to the executed process. The total FDs // passed will be 3 + PreserveFDs. // set tags as `json:"-"` for not supported remote + // Optional. PreserveFDs uint `json:"-"` // Timezone is the timezone inside the container. // Local means it has the same timezone as the host machine + // Optional. Timezone string `json:"timezone,omitempty"` + // DependencyContainers is an array of containers this container + // depends on. Dependency containers must be started before this + // container. Dependencies can be specified by name or full/partial ID. + // Optional. + DependencyContainers []string `json:"dependencyContainers,omitempty"` } // ContainerStorageConfig contains information on the storage configuration of a diff --git a/test/buildah-bud/buildah-tests.diff b/test/buildah-bud/buildah-tests.diff index ad35e5926..141bd9266 100644 --- a/test/buildah-bud/buildah-tests.diff +++ b/test/buildah-bud/buildah-tests.diff @@ -1,16 +1,16 @@ -From a49a2e48421c6f3bb1a56ae372de1f3d1a45d1f1 Mon Sep 17 00:00:00 2001 +From 47cc8f54d272039bdab389b29c8b2b5bb085e923 Mon Sep 17 00:00:00 2001 From: Ed Santiago <santiago@redhat.com> Date: Tue, 9 Feb 2021 17:28:05 -0700 Subject: [PATCH] tweaks for running buildah tests under podman Signed-off-by: Ed Santiago <santiago@redhat.com> --- - tests/bud.bats | 26 ++++++++++++++++---------- + tests/bud.bats | 22 +++++++++++++--------- tests/helpers.bash | 28 ++++++++++++++++++++++++---- - 2 files changed, 40 insertions(+), 14 deletions(-) + 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/tests/bud.bats b/tests/bud.bats -index cf55d9a4..60cb6f96 100644 +index fe3af27a..d1ccced5 100644 --- a/tests/bud.bats +++ b/tests/bud.bats @@ -4,7 +4,7 @@ load helpers @@ -30,7 +30,7 @@ index cf55d9a4..60cb6f96 100644 run_buildah 125 bud /tmp/tmpdockerfile/ -t blabla check_options_flag_err "-t" -@@ -1416,13 +1417,13 @@ function _test_http() { +@@ -1436,13 +1437,13 @@ function _test_http() { @test "bud with dir for file but no Dockerfile in dir" { target=alpine-image run_buildah 125 bud --signature-policy ${TESTSDIR}/policy.json -t ${target} -f ${TESTSDIR}/bud/empty-dir ${TESTSDIR}/bud/empty-dir @@ -46,18 +46,7 @@ index cf55d9a4..60cb6f96 100644 } @test "bud with ARG before FROM default value" { -@@ -1834,7 +1835,9 @@ _EOF - run_buildah bud --signature-policy ${TESTSDIR}/policy.json --layers -t test-img-2 --build-arg TEST=foo -f Dockerfile4 ${TESTSDIR}/bud/build-arg - run_buildah inspect -f '{{.FromImageID}}' test-img-2 - argsid="$output" -- [[ "$argsid" != "$initialid" ]] -+ if [[ "$argsid" == "$initialid" ]]; then -+ die ".FromImageID of test-img-2 ($argsid) == same as test-img, it should be different" -+ fi - - # Set the build-arg via an ENV in the local environment and verify that the cached layers are not used - export TEST=bar -@@ -1887,6 +1890,7 @@ _EOF +@@ -1913,6 +1914,7 @@ _EOF } @test "bud without any arguments should succeed" { @@ -65,7 +54,7 @@ index cf55d9a4..60cb6f96 100644 cd ${TESTSDIR}/bud/from-scratch run_buildah bud --signature-policy ${TESTSDIR}/policy.json } -@@ -1894,7 +1898,7 @@ _EOF +@@ -1920,7 +1922,7 @@ _EOF @test "bud without any arguments should fail when no Dockerfile exist" { cd $(mktemp -d) run_buildah 125 bud --signature-policy ${TESTSDIR}/policy.json @@ -74,7 +63,7 @@ index cf55d9a4..60cb6f96 100644 } @test "bud with specified context should fail if directory contains no Dockerfile" { -@@ -1907,16 +1911,17 @@ _EOF +@@ -1933,16 +1935,17 @@ _EOF DIR=$(mktemp -d) mkdir -p "$DIR"/Dockerfile run_buildah 125 bud --signature-policy ${TESTSDIR}/policy.json "$DIR" @@ -94,7 +83,7 @@ index cf55d9a4..60cb6f96 100644 DIR=$(mktemp -d) echo "FROM alpine" > "$DIR"/Dockerfile run_buildah 0 bud --signature-policy ${TESTSDIR}/policy.json "$DIR"/Dockerfile -@@ -1968,7 +1973,7 @@ _EOF +@@ -1994,7 +1997,7 @@ _EOF @test "bud-squash-hardlinks" { _prefetch busybox @@ -103,7 +92,7 @@ index cf55d9a4..60cb6f96 100644 } @test "bud with additional directory of devices" { -@@ -2134,6 +2139,7 @@ _EOF +@@ -2159,6 +2162,7 @@ _EOF } @test "bud with Containerfile should fail with nonexistent authfile" { @@ -111,7 +100,7 @@ index cf55d9a4..60cb6f96 100644 target=alpine-image run_buildah 125 bud --authfile /tmp/nonexistent --signature-policy ${TESTSDIR}/policy.json -t ${target} ${TESTSDIR}/bud/containerfile } -@@ -2261,6 +2267,7 @@ EOM +@@ -2286,6 +2290,7 @@ EOM } @test "bud with encrypted FROM image" { @@ -119,7 +108,7 @@ index cf55d9a4..60cb6f96 100644 _prefetch busybox mkdir ${TESTDIR}/tmp openssl genrsa -out ${TESTDIR}/tmp/mykey.pem 1024 -@@ -2333,8 +2340,6 @@ EOM +@@ -2358,8 +2363,6 @@ EOM _prefetch alpine run_buildah bud --timestamp=0 --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json -t timestamp -f Dockerfile.1 ${TESTSDIR}/bud/cache-stages cid=$output @@ -128,7 +117,7 @@ index cf55d9a4..60cb6f96 100644 run_buildah inspect --format '{{ .OCIv1.Created }}' timestamp expect_output --substring "1970-01-01" run_buildah inspect --format '{{ .History }}' timestamp -@@ -2594,6 +2599,7 @@ _EOF +@@ -2619,6 +2622,7 @@ _EOF } @test "bud with --arch flag" { @@ -137,18 +126,18 @@ index cf55d9a4..60cb6f96 100644 mytmpdir=${TESTDIR}/my-dir mkdir -p ${mytmpdir} diff --git a/tests/helpers.bash b/tests/helpers.bash -index b28fd2c3..d42a6b82 100644 +index 99c290af..c5572840 100644 --- a/tests/helpers.bash +++ b/tests/helpers.bash @@ -70,7 +70,7 @@ function _prefetch() { - mkdir -p ${_BUILDAH_IMAGE_CACHEDIR} - fi + mkdir -p ${_BUILDAH_IMAGE_CACHEDIR} + fi -- local _podman_opts="--root ${TESTDIR}/root --storage-driver ${STORAGE_DRIVER}" -+ local _podman_opts="--root ${TESTDIR}/root --runroot ${TESTDIR}/runroot --storage-driver ${STORAGE_DRIVER}" +- local _podman_opts="--root ${TESTDIR}/root --storage-driver ${STORAGE_DRIVER}" ++ local _podman_opts="--root ${TESTDIR}/root --runroot ${TESTDIR}/runroot --storage-driver ${STORAGE_DRIVER}" - for img in "$@"; do - echo "# [checking for: $img]" >&2 + for img in "$@"; do + echo "# [checking for: $img]" >&2 @@ -138,15 +138,35 @@ function run_buildah() { --retry) retry=3; shift;; # retry network flakes esac diff --git a/test/buildah-bud/run-buildah-bud-tests b/test/buildah-bud/run-buildah-bud-tests index 67c8fdfa4..1265e67d5 100755 --- a/test/buildah-bud/run-buildah-bud-tests +++ b/test/buildah-bud/run-buildah-bud-tests @@ -72,7 +72,7 @@ function die() { # From here on out, any unexpected abort will try to offer helpful hints failhint= -trap 'if [[ $? != 0 ]]; then if [[ -n $failhint ]]; then echo;echo "***************************************";echo $failhint;echo;echo "Please see $BUD_TEST_DIR_REL/README.md for advice";fi;fi' 0 +trap 'if [[ $? != 0 ]]; then if [[ -n $failhint ]]; then echo;echo "***************************************";echo "$failhint";echo;echo "Please see $BUD_TEST_DIR_REL/README.md for advice";fi;fi' 0 # Find the version of buildah we've vendored in, so we can run the right tests buildah_version=$(awk "\$1 == \"$BUILDAH_REPO\" { print \$2 }" <go.mod) @@ -110,10 +110,27 @@ if [[ -n $do_checkout ]]; then die "Directory already exists: $buildah_dir" fi + # buildah_version should usually be vX.Y, but sometimes a PR under test + # will need a special unreleased version (go calls then "pseudoversions"). + # In the usual case, we can do a shallow git clone: + shallow_checkout="--branch $buildah_version" + if [[ $buildah_version =~ .*-.*\.[0-9]{14}-.* ]]; then + # ...but with a pseudoversion, we must git-clone the entire repo, + # then do a git checkout within it + shallow_checkout= + fi + failhint="'git clone' failed - this should never happen!" - (set -x;git clone -q --branch $buildah_version https://$BUILDAH_REPO $buildah_dir) + (set -x;git clone -q $shallow_checkout https://$BUILDAH_REPO $buildah_dir) cd $buildah_dir + if [[ -z $shallow_checkout ]]; then + # extract the SHA (rightmost field) from, e.g., v1.2-YYYMMDD-<sha> + sha=${buildah_version##*-} + + failhint="'git checkout $sha' failed - this should never happen!" + (set -x;git checkout -q $sha) + fi # Give it a recognizable tag; this will be useful if we need to update # the set of patches @@ -123,18 +140,22 @@ if [[ -n $do_checkout ]]; then failhint="error building buildah. This should never happen." (set -x;make bin/buildah) - # Apply custom patches. We do this _after_ building, although it shouldn't - # matter because these patches should only apply to test scripts. - failhint=" -Error applying patch file. This can happen when you vendor in a new buildah." - (set -x;git am <$PATCHES) - + # The upcoming patch may fail. Before we try it, create a helper script + # for a developer to push a new set of diffs to podman-land. failhint= sed -e "s,\[BASETAG\],${BASE_TAG},g" \ -e "s,\[BUILDAHREPO\],${BUILDAH_REPO},g" \ < ${BUD_TEST_DIR}/make-new-buildah-diffs \ > make-new-buildah-diffs chmod 755 make-new-buildah-diffs + + # Apply custom patches. We do this _after_ building, although it shouldn't + # matter because these patches should only apply to test scripts. + failhint=" +Error applying patch file. This can happen when you vendor in a new buildah. + +Look for '*.rej' files to resolve the conflict(s) manually." + (set -x;git am --reject <$PATCHES) else # Called with --no-checkout test -d $buildah_dir || die "Called with --no-checkout, but $buildah_dir does not exist" diff --git a/test/compose/test-compose b/test/compose/test-compose index 704c71a9f..7693041ac 100755 --- a/test/compose/test-compose +++ b/test/compose/test-compose @@ -163,7 +163,13 @@ function test_port() { local op="$2" # '=' or '~' local expect="$3" # what to expect from curl output - local actual=$(curl --retry 10 --retry-all-errors -s http://127.0.0.1:$port/) + local actual=$(curl --retry 3 --retry-all-errors -s http://127.0.0.1:$port/) + # The test is flaking with an empty result. The curl retry doesn't solve this. + # If the result is empty sleep one second and try again. + if [[ "$actual" == "" ]]; then + sleep 1 + local actual=$(curl --retry 3 --retry-all-errors -s http://127.0.0.1:$port/) + fi local curl_rc=$? if [ $curl_rc -ne 0 ]; then _show_ok 0 "$testname - curl failed with status $curl_rc" diff --git a/test/e2e/build_test.go b/test/e2e/build_test.go index 4f337116e..6255690b1 100644 --- a/test/e2e/build_test.go +++ b/test/e2e/build_test.go @@ -566,4 +566,42 @@ RUN echo hello`, ALPINE) Expect(session.OutputToString()).To(ContainSubstring("(user)")) Expect(session.OutputToString()).To(ContainSubstring("(elapsed)")) }) + + It("podman build --arch --os flag", func() { + containerfile := `FROM scratch` + containerfilePath := filepath.Join(podmanTest.TempDir, "Containerfile") + err := ioutil.WriteFile(containerfilePath, []byte(containerfile), 0755) + Expect(err).To(BeNil()) + session := podmanTest.Podman([]string{"build", "--pull-never", "-t", "test", "--arch", "foo", "--os", "bar", "--file", containerfilePath, podmanTest.TempDir}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + inspect := podmanTest.Podman([]string{"image", "inspect", "--format", "{{ .Architecture }}", "test"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.OutputToString()).To(Equal("foo")) + + inspect = podmanTest.Podman([]string{"image", "inspect", "--format", "{{ .Os }}", "test"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.OutputToString()).To(Equal("bar")) + + }) + + It("podman build --os windows flag", func() { + containerfile := `FROM scratch` + containerfilePath := filepath.Join(podmanTest.TempDir, "Containerfile") + err := ioutil.WriteFile(containerfilePath, []byte(containerfile), 0755) + Expect(err).To(BeNil()) + session := podmanTest.Podman([]string{"build", "--pull-never", "-t", "test", "--os", "windows", "--file", containerfilePath, podmanTest.TempDir}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + inspect := podmanTest.Podman([]string{"image", "inspect", "--format", "{{ .Architecture }}", "test"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.OutputToString()).To(Equal(runtime.GOARCH)) + + inspect = podmanTest.Podman([]string{"image", "inspect", "--format", "{{ .Os }}", "test"}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.OutputToString()).To(Equal("windows")) + + }) }) diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index df86eab15..e6f63a391 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -119,6 +119,19 @@ var _ = Describe("Podman exec", func() { Expect(session.ExitCode()).To(Equal(100)) }) + It("podman exec in keep-id container drops privileges", func() { + SkipIfNotRootless("This function is not enabled for rootful podman") + ctrName := "testctr1" + testCtr := podmanTest.Podman([]string{"run", "-d", "--name", ctrName, "--userns=keep-id", ALPINE, "top"}) + testCtr.WaitWithDefaultTimeout() + Expect(testCtr.ExitCode()).To(Equal(0)) + + session := podmanTest.Podman([]string{"exec", ctrName, "grep", "CapEff", "/proc/self/status"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("0000000000000000")) + }) + It("podman exec --privileged", func() { session := podmanTest.Podman([]string{"run", "--privileged", "--rm", ALPINE, "sh", "-c", "grep ^CapBnd /proc/self/status | cut -f 2"}) session.WaitWithDefaultTimeout() @@ -143,7 +156,6 @@ var _ = Describe("Podman exec", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring(bndPerms)) - }) It("podman exec --privileged", func() { diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 23930b4f7..cefe00655 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1588,4 +1588,29 @@ WORKDIR /madethis`, BB) Expect(session.OutputToString()).To(ContainSubstring("mysecret")) }) + + It("podman run --requires", func() { + depName := "ctr1" + depContainer := podmanTest.Podman([]string{"create", "--name", depName, ALPINE, "top"}) + depContainer.WaitWithDefaultTimeout() + Expect(depContainer.ExitCode()).To(Equal(0)) + + mainName := "ctr2" + mainContainer := podmanTest.Podman([]string{"run", "--name", mainName, "--requires", depName, "-d", ALPINE, "top"}) + mainContainer.WaitWithDefaultTimeout() + Expect(mainContainer.ExitCode()).To(Equal(0)) + + stop := podmanTest.Podman([]string{"stop", "--all"}) + stop.WaitWithDefaultTimeout() + Expect(stop.ExitCode()).To(Equal(0)) + + start := podmanTest.Podman([]string{"start", mainName}) + start.WaitWithDefaultTimeout() + Expect(start.ExitCode()).To(Equal(0)) + + running := podmanTest.Podman([]string{"ps", "-q"}) + running.WaitWithDefaultTimeout() + Expect(running.ExitCode()).To(Equal(0)) + Expect(len(running.OutputToStringArray())).To(Equal(2)) + }) }) diff --git a/test/system/005-info.bats b/test/system/005-info.bats index c0af2e937..ed341dd17 100644 --- a/test/system/005-info.bats +++ b/test/system/005-info.bats @@ -53,6 +53,27 @@ store.imageStore.number | 1 } +# 2021-04-06 discussed in watercooler: RHEL must never use crun, even if +# using cgroups v2. +@test "podman info - RHEL8 must use runc" { + local osrelease=/etc/os-release + test -e $osrelease || skip "Not a RHEL system (no $osrelease)" + + local osname=$(source $osrelease; echo $NAME) + if [[ $osname =~ Red.Hat || $osname =~ CentOS ]]; then + # Version can include minor; strip off first dot an all beyond it + local osver=$(source $osrelease; echo $VERSION_ID) + test ${osver%%.*} -le 8 || skip "$osname $osver > RHEL8" + + # RHEL or CentOS 8. + # FIXME: what does 'CentOS 8' even mean? What is $VERSION_ID in CentOS? + run_podman info --format '{{.Host.OCIRuntime.Name}}' + is "$output" "runc" "$osname only supports OCI Runtime = runc" + else + skip "only applicable on RHEL, this is $osname" + fi +} + @test "podman info --storage-opt='' " { skip_if_remote "--storage-opt flag is not supported for remote" skip_if_rootless "storage opts are required for rootless running" diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index 804dd46b1..cda054b15 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -209,4 +209,19 @@ load helpers run_podman rm -f $cid } +@test "podman rootless cni adds /usr/sbin to PATH" { + is_rootless || skip "only meaningful for rootless" + + local mynetname=testnet-$(random_string 10) + run_podman network create $mynetname + + # Test that rootless cni adds /usr/sbin to $PATH + # iptables is located under /usr/sbin and is needed for the CNI plugins. + # Debian doesn't add /usr/sbin to $PATH for rootless users so we have to add it. + PATH=/usr/local/bin:/usr/bin run_podman run --rm --network $mynetname $IMAGE ip addr + is "$output" ".*eth0.*" "Interface eth0 not found in ip addr output" + + run_podman network rm -f $mynetname +} + # vim: filetype=sh diff --git a/vendor/github.com/containers/buildah/.cirrus.yml b/vendor/github.com/containers/buildah/.cirrus.yml index 9c875a8fd..32c711be8 100644 --- a/vendor/github.com/containers/buildah/.cirrus.yml +++ b/vendor/github.com/containers/buildah/.cirrus.yml @@ -164,7 +164,7 @@ conformance_task: gce_instance: image_name: "${UBUNTU_CACHE_IMAGE_NAME}" - timeout_in: 20m + timeout_in: 25m setup_script: '${SCRIPT_BASE}/setup.sh |& ${_TIMESTAMP}' conformance_test_script: '${SCRIPT_BASE}/test.sh conformance |& ${_TIMESTAMP}' diff --git a/vendor/github.com/containers/buildah/Makefile b/vendor/github.com/containers/buildah/Makefile index fad4a2107..9ff59df55 100644 --- a/vendor/github.com/containers/buildah/Makefile +++ b/vendor/github.com/containers/buildah/Makefile @@ -149,7 +149,7 @@ install.runc: .PHONY: test-conformance test-conformance: - $(GO_TEST) -v -tags "$(STORAGETAGS) $(SECURITYTAGS)" -cover -timeout 15m ./tests/conformance + $(GO_TEST) -v -tags "$(STORAGETAGS) $(SECURITYTAGS)" -cover -timeout 20m ./tests/conformance .PHONY: test-integration test-integration: install.tools diff --git a/vendor/github.com/containers/buildah/chroot/run.go b/vendor/github.com/containers/buildah/chroot/run.go index 39ad88b2b..7cb1d710e 100644 --- a/vendor/github.com/containers/buildah/chroot/run.go +++ b/vendor/github.com/containers/buildah/chroot/run.go @@ -20,6 +20,7 @@ import ( "unsafe" "github.com/containers/buildah/bind" + "github.com/containers/buildah/copier" "github.com/containers/buildah/util" "github.com/containers/storage/pkg/ioutils" "github.com/containers/storage/pkg/mount" @@ -1161,7 +1162,18 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func( } } target := filepath.Join(spec.Root.Path, m.Destination) - if _, err := os.Stat(target); err != nil { + // Check if target is a symlink + stat, err := os.Lstat(target) + // If target is a symlink, follow the link and ensure the destination exists + if err == nil && stat != nil && (stat.Mode()&os.ModeSymlink != 0) { + target, err = copier.Eval(spec.Root.Path, m.Destination, copier.EvalOptions{}) + if err != nil { + return nil, errors.Wrapf(err, "evaluating symlink %q", target) + } + // Stat the destination of the evaluated symlink + _, err = os.Stat(target) + } + if err != nil { // If the target can't be stat()ted, check the error. if !os.IsNotExist(err) { return undoBinds, errors.Wrapf(err, "error examining %q for mounting in mount namespace", target) diff --git a/vendor/github.com/containers/buildah/define/types.go b/vendor/github.com/containers/buildah/define/types.go index 5f3e29e62..6d4809cc0 100644 --- a/vendor/github.com/containers/buildah/define/types.go +++ b/vendor/github.com/containers/buildah/define/types.go @@ -28,7 +28,7 @@ const ( Package = "buildah" // Version for the Package. Bump version in contrib/rpm/buildah.spec // too. - Version = "1.20.0" + Version = "1.20.1-dev" // DefaultRuntime if containers.conf fails. DefaultRuntime = "runc" @@ -166,7 +166,7 @@ func cloneToDirectory(url, dir string) error { cmd = exec.Command("git", "clone", url, dir) } else { logrus.Debugf("cloning repo %q and branch %q to %q", gitBranch[0], gitBranch[1], dir) - cmd = exec.Command("git", "clone", "-b", gitBranch[1], gitBranch[0], dir) + cmd = exec.Command("git", "clone", "--recurse-submodules", "-b", gitBranch[1], gitBranch[0], dir) } return cmd.Run() } diff --git a/vendor/github.com/containers/buildah/go.mod b/vendor/github.com/containers/buildah/go.mod index fa37132f4..075bdfb01 100644 --- a/vendor/github.com/containers/buildah/go.mod +++ b/vendor/github.com/containers/buildah/go.mod @@ -4,7 +4,7 @@ go 1.12 require ( github.com/containernetworking/cni v0.8.1 - github.com/containers/common v0.35.3 + github.com/containers/common v0.35.4 github.com/containers/image/v5 v5.10.5 github.com/containers/ocicrypt v1.1.0 github.com/containers/storage v1.28.1 diff --git a/vendor/github.com/containers/buildah/go.sum b/vendor/github.com/containers/buildah/go.sum index c20348bb2..6a48853ac 100644 --- a/vendor/github.com/containers/buildah/go.sum +++ b/vendor/github.com/containers/buildah/go.sum @@ -165,8 +165,8 @@ github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ github.com/containernetworking/cni v0.8.1 h1:7zpDnQ3T3s4ucOuJ/ZCLrYBxzkg0AELFfII3Epo9TmI= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= -github.com/containers/common v0.35.3 h1:6tEBSIHlJzpmt35zA1ZcjBqbtUilAHDWaa7buPvaqWY= -github.com/containers/common v0.35.3/go.mod h1:rMzxgD7nMGw++cEbsp+NZv0UJO4rgXbm7F7IbJPTwIE= +github.com/containers/common v0.35.4 h1:szyWRncsHkBwCVpu1dkEOXUjkwCetlfcLmKJTwo1Sp8= +github.com/containers/common v0.35.4/go.mod h1:rMzxgD7nMGw++cEbsp+NZv0UJO4rgXbm7F7IbJPTwIE= github.com/containers/image/v5 v5.10.5 h1:VK1UbsZMzjdw5Xqr3Im9h4iOqHWU0naFs+I78kavc7I= github.com/containers/image/v5 v5.10.5/go.mod h1:SgIbWEedCNBbn2FI5cH0/jed1Ecy2s8XK5zTxvJTzII= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= diff --git a/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go b/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go index 3f0177226..f3876cd13 100644 --- a/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go +++ b/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go @@ -13,6 +13,7 @@ import ( "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/image" "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/compression" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/types" "github.com/containers/storage/pkg/archive" @@ -301,25 +302,32 @@ func (s *blobCacheSource) LayerInfosForCopy(ctx context.Context, instanceDigest alternate = filepath.Join(filepath.Dir(alternate), makeFilename(digest.Digest(replaceDigest), false)) fileInfo, err := os.Stat(alternate) if err == nil { - logrus.Debugf("suggesting cached blob with digest %q and compression %v in place of blob with digest %q", string(replaceDigest), s.reference.compress, info.Digest.String()) - info.Digest = digest.Digest(replaceDigest) - info.Size = fileInfo.Size() switch info.MediaType { case v1.MediaTypeImageLayer, v1.MediaTypeImageLayerGzip: switch s.reference.compress { case types.Compress: info.MediaType = v1.MediaTypeImageLayerGzip + info.CompressionAlgorithm = &compression.Gzip case types.Decompress: info.MediaType = v1.MediaTypeImageLayer + info.CompressionAlgorithm = nil } case docker.V2S2MediaTypeUncompressedLayer, manifest.DockerV2Schema2LayerMediaType: switch s.reference.compress { case types.Compress: info.MediaType = manifest.DockerV2Schema2LayerMediaType + info.CompressionAlgorithm = &compression.Gzip case types.Decompress: - info.MediaType = docker.V2S2MediaTypeUncompressedLayer + // nope, not going to suggest anything, it's not allowed by the spec + replacedInfos = append(replacedInfos, info) + continue } } + logrus.Debugf("suggesting cached blob with digest %q, type %q, and compression %v in place of blob with digest %q", string(replaceDigest), info.MediaType, s.reference.compress, info.Digest.String()) + info.CompressionOperation = s.reference.compress + info.Digest = digest.Digest(replaceDigest) + info.Size = fileInfo.Size() + logrus.Debugf("info = %#v", info) } } replacedInfos = append(replacedInfos, info) @@ -422,8 +430,9 @@ func (d *blobCacheDestination) PutBlob(ctx context.Context, stream io.Reader, in var err error var n int var alternateDigest digest.Digest + var closer io.Closer wg := new(sync.WaitGroup) - defer wg.Wait() + needToWait := false compression := archive.Uncompressed if inputInfo.Digest != "" { filename := filepath.Join(d.reference.directory, makeFilename(inputInfo.Digest, isConfig)) @@ -458,7 +467,7 @@ func (d *blobCacheDestination) PutBlob(ctx context.Context, stream io.Reader, in if n >= len(initial) { compression = archive.DetectCompression(initial[:n]) } - if compression != archive.Uncompressed { + if compression == archive.Gzip { // The stream is compressed, so create a file which we'll // use to store a decompressed copy. decompressedTemp, err2 := ioutil.TempFile(d.reference.directory, makeFilename(inputInfo.Digest, isConfig)) @@ -470,10 +479,11 @@ func (d *blobCacheDestination) PutBlob(ctx context.Context, stream io.Reader, in // closing the writing end of the pipe after // PutBlob() returns. decompressReader, decompressWriter := io.Pipe() - defer decompressWriter.Close() + closer = decompressWriter stream = io.TeeReader(stream, decompressWriter) // Let saveStream() close the reading end and handle the temporary file. wg.Add(1) + needToWait = true go saveStream(wg, decompressReader, decompressedTemp, filename, inputInfo.Digest, isConfig, &alternateDigest) } } @@ -481,6 +491,12 @@ func (d *blobCacheDestination) PutBlob(ctx context.Context, stream io.Reader, in } } newBlobInfo, err := d.destination.PutBlob(ctx, stream, inputInfo, cache, isConfig) + if closer != nil { + closer.Close() + } + if needToWait { + wg.Wait() + } if err != nil { return newBlobInfo, errors.Wrapf(err, "error storing blob to image destination for cache %q", transports.ImageName(d.reference)) } diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse.go b/vendor/github.com/containers/buildah/pkg/parse/parse.go index 9497ca4b6..2ae07efe9 100644 --- a/vendor/github.com/containers/buildah/pkg/parse/parse.go +++ b/vendor/github.com/containers/buildah/pkg/parse/parse.go @@ -629,7 +629,7 @@ func SystemContextFromOptions(c *cobra.Command) (*types.SystemContext, error) { } if c.Flag("platform") != nil && c.Flag("platform").Changed { if platform, err := c.Flags().GetString("platform"); err == nil { - os, arch, variant, err := parsePlatform(platform) + os, arch, variant, err := Platform(platform) if err != nil { return nil, err } @@ -672,7 +672,7 @@ func PlatformFromOptions(c *cobra.Command) (os, arch string, err error) { if c.Flag("platform").Changed { if pf, err := c.Flags().GetString("platform"); err == nil { - selectedOS, selectedArch, _, err := parsePlatform(pf) + selectedOS, selectedArch, _, err := Platform(pf) if err != nil { return "", "", errors.Wrap(err, "unable to parse platform") } @@ -691,7 +691,8 @@ func DefaultPlatform() string { return runtime.GOOS + platformSep + runtime.GOARCH } -func parsePlatform(platform string) (os, arch, variant string, err error) { +// Platform separates the platform string into os, arch and variant +func Platform(platform string) (os, arch, variant string, err error) { split := strings.Split(platform, platformSep) if len(split) < 2 { return "", "", "", errors.Errorf("invalid platform syntax for %q (use OS/ARCH)", platform) diff --git a/vendor/github.com/containers/buildah/run_linux.go b/vendor/github.com/containers/buildah/run_linux.go index ffbb36b7b..6356d2602 100644 --- a/vendor/github.com/containers/buildah/run_linux.go +++ b/vendor/github.com/containers/buildah/run_linux.go @@ -359,7 +359,17 @@ func runSetupBuiltinVolumes(mountLabel, mountPoint, containerDir string, builtin } initializeVolume = true } - stat, err := os.Stat(srcPath) + // Check if srcPath is a symlink + stat, err := os.Lstat(srcPath) + // If srcPath is a symlink, follow the link and ensure the destination exists + if err == nil && stat != nil && (stat.Mode()&os.ModeSymlink != 0) { + srcPath, err = copier.Eval(mountPoint, volume, copier.EvalOptions{}) + if err != nil { + return nil, errors.Wrapf(err, "evaluating symlink %q", srcPath) + } + // Stat the destination of the evaluated symlink + stat, err = os.Stat(srcPath) + } if err != nil { if !os.IsNotExist(err) { return nil, err @@ -519,8 +529,9 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st return err } + allMounts := util.SortMounts(append(append(append(append(append(volumes, builtins...), secretMounts...), bindFileMounts...), specMounts...), sysfsMount...)) // Add them all, in the preferred order, except where they conflict with something that was previously added. - for _, mount := range append(append(append(append(append(volumes, builtins...), secretMounts...), bindFileMounts...), specMounts...), sysfsMount...) { + for _, mount := range allMounts { if haveMount(mount.Destination) { // Already mounting something there, no need to bother with this one. continue diff --git a/vendor/github.com/containers/buildah/util/util.go b/vendor/github.com/containers/buildah/util/util.go index 419f905e1..b3fae6003 100644 --- a/vendor/github.com/containers/buildah/util/util.go +++ b/vendor/github.com/containers/buildah/util/util.go @@ -6,6 +6,8 @@ import ( "net/url" "os" "path" + "path/filepath" + "sort" "strings" "sync" "syscall" @@ -474,3 +476,26 @@ func MergeEnv(defaults, overrides []string) []string { } return s } + +type byDestination []specs.Mount + +func (m byDestination) Len() int { + return len(m) +} + +func (m byDestination) Less(i, j int) bool { + return m.parts(i) < m.parts(j) +} + +func (m byDestination) Swap(i, j int) { + m[i], m[j] = m[j], m[i] +} + +func (m byDestination) parts(i int) int { + return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator)) +} + +func SortMounts(m []specs.Mount) []specs.Mount { + sort.Sort(byDestination(m)) + return m +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 52ed2101e..ffa4a7858 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -74,7 +74,7 @@ github.com/containernetworking/plugins/pkg/utils/hwaddr github.com/containernetworking/plugins/pkg/utils/sysctl github.com/containernetworking/plugins/plugins/ipam/host-local/backend github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator -# github.com/containers/buildah v1.20.0 +# github.com/containers/buildah v1.20.1-0.20210402144408-36a37402d0c8 github.com/containers/buildah github.com/containers/buildah/bind github.com/containers/buildah/chroot |