From 9aad7ca2031443ec174d562987a0d19c642083a8 Mon Sep 17 00:00:00 2001 From: Jeffrey Clark Date: Fri, 3 May 2024 19:11:24 -0500 Subject: [PATCH 1/4] automatically manage ownership in container to match host deprecate /root volume, replaced by /workspace new entrypoint script: * creates a user in the container with the same uid and gid of the volume mapped workspace directory, or the --user parameter, and runs the pdk command as that user in the container. With one exception, if the workspace directory is owned by root on the host * support for volume mapping pdk cache to /cache * failure message, if /workspace is not mapped (or writable) * warning message, if /workspace or /cache have owners or permissions that may not work * startup message, if /cache is not mounted in container (performance) --- Dockerfile | 9 ++++++- entrypoint.sh | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100755 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index a174b40..446ff22 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,11 @@ WORKDIR /root ADD install-pdk-release.sh . ADD install-onceover.sh . ADD pdk-release.env . +COPY entrypoint.sh /.entrypoint.sh + +RUN passwd -d root && \ + mkdir /cache && \ + chmod a+rwx /cache RUN apt-get update && \ apt-get install -y curl openssh-client && \ @@ -25,4 +30,6 @@ ENV PATH="${PATH}:/opt/puppetlabs/pdk/private/git/bin" ENV PDK_DISABLE_ANALYTICS=true ENV LANG=C.UTF-8 -ENTRYPOINT ["/opt/puppetlabs/pdk/bin/pdk"] +WORKDIR /workspace + +ENTRYPOINT ["/.entrypoint.sh"] diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..09d3585 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +# re-entrant script to support automatically switching to an unprivileged user +# that matches the ownership of the RUN_VOLUME (see below) + +set -e + +RUN_USER=pdk +RUN_VOLUME=/workspace + +[ -z "${UID}" ] && UID=$(id -u) +[ -z "${GID}" ] && GID=$(id -g) + +[ "$UID" -ne 0 ] && RUNNING_NON_ROOT=1 + +# check if required path is mounted +# check for deprecated /root volume +if grep -sq " /root " < /proc/mounts ; then + [ -z "$ENTRYPOINT_RELOAD" ] && echo >&2 "mounting a volume to /root in the container is deprecated, use /workspace instead" + RUN_VOLUME=/root +elif ! grep -sq " ${RUN_VOLUME} " < /proc/mounts ; then + echo >&2 "error: ${RUN_VOLUME} in the container is not mounted." ; exit 1 +fi + +create_user() { + if [ "$1" -gt 0 ] ; then + if [ "$2" -gt 0 ] ; then + su - -c "groupadd -g $2 $RUN_USER" 2>/dev/null || true + fi + su - -c "useradd -d /cache -u $1 -g $2 $RUN_USER ; chown $RUN_USER: /cache ; passwd -d $RUN_USER >/dev/null" + fi +} + +# skip if re-running under newly created user +if [ -z "$ENTRYPOINT_RELOAD" ] ; then + if [ -z "$RUNNING_NON_ROOT" ] ; then + UID=$(stat -c '%u' "$RUN_VOLUME") + GID=$(stat -c '%g' "$RUN_VOLUME") + [ "$UID" -eq 0 ] && RUN_USER="root" + fi + create_user "$UID" "$GID" + # re-run with new user + exec su - $RUN_USER -c "cd $RUN_VOLUME ; ENTRYPOINT_RELOAD=1 $0 $*" + exit +fi + +# sanity check supported volumes +for volume in ${RUN_VOLUME} /cache ; do + if [ ! -w "$volume" ] ; then + echo >&2 "error: unable to write to ${volume}. Ensure permissions are correct on the host." ; exit 1 + fi + if ! find "$volume/." -maxdepth 1 -name '.' \( -uid "$UID" -a -perm -u+rw \) -o \( -group "$GID" -a -perm -g+rw \) -exec true {} + ; then + echo >&2 "warning: pdk may not function properly with the user/group ownership or permissions on ${volume}." + fi +done + +# recommend cache path is mounted +if ! grep -sq " /cache " < /proc/mounts ; then + echo >&2 "mount a volume to /cache in the container to improve performance." +fi + +export PATH="${PATH}:/opt/puppetlabs/pdk/private/git/bin" +export PDK_DISABLE_ANALYTICS=true +export LANG=C.UTF-8 + +exec /opt/puppetlabs/pdk/bin/pdk "$@" From 39f321d2f3e088f6cf45812a0086a522792c516f Mon Sep 17 00:00:00 2001 From: Jeffrey Clark Date: Fri, 3 May 2024 20:52:11 -0500 Subject: [PATCH 2/4] new ci test workflow --- .github/workflows/ci.yml | 76 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e1ffc0a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,76 @@ +name: ci + +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build PDK Container + id: build + uses: docker/build-push-action@v5 + with: + context: . + push: false + load: true + tags: puppet/pdk:${{ github.sha }} + + - name: Test with no workspace volume + run: | + if docker run --rm ${{ steps.build.outputs.imageid }} --version 2>"$GITHUB_WORKSPACE/.errout" ; then + echo '::error::expected an error that was not returned' + exit 1 + fi + grep -s 'error: /workspace in the container is not mounted' < "$GITHUB_WORKSPACE/.errout" + + - name: Test with a workspace volume + run: | + docker run --rm -v `pwd`:/workspace ${{ steps.build.outputs.imageid }} --version 2>"$GITHUB_WORKSPACE/.errout" | grep -E '^[[:digit:]\.]+$' + grep -s 'mount a volume to /cache in the container to improve performance' < "$GITHUB_WORKSPACE/.errout" + + - name: Test create new module + run: | + WORKSPACE_OWNER="$(stat -c '%u:%g' .)" + docker run --rm -v `pwd`:/workspace ${{ steps.build.outputs.imageid }} new module dockertest --skip-interview + if [ "${WORKSPACE_OWNER}" != "$(stat -c '%u:%g' dockertest)" ] ; then + echo "::error::pdk in container failed to run with same uid and gid of host workspace" + exit 1 + fi + cd dockertest + docker run --rm -v `pwd`:/workspace ${{ steps.build.outputs.imageid }} new class test + docker run --rm -v `pwd`:/workspace ${{ steps.build.outputs.imageid }} validate + docker run --rm -v `pwd`:/workspace ${{ steps.build.outputs.imageid }} test unit + + - name: Test running with root workspace + run: | + sudo cp -r dockertest roottest + cd roottest + docker run --rm -v `pwd`:/workspace ${{ steps.build.outputs.imageid }} new class root + + - name: Test running with workspace ownership not matching user + run: | + cd roottest + if docker run --rm --user $UID:$GID -v `pwd`:/workspace ${{ steps.build.outputs.imageid }} new class root 2>"$GITHUB_WORKSPACE/.errout" ; then + echo '::error::expected an error that was not returned' + exit 1 + fi + grep -s 'error: unable to write to /workspace' < "$GITHUB_WORKSPACE/.errout" + + - name: Test deprecated /root volume + run: | + cd roottest + docker run --rm -v `pwd`:/root ${{ steps.build.outputs.imageid }} new class toor 2>"$GITHUB_WORKSPACE/.errout" + grep -s 'mounting a volume to /root in the container is deprecated' < "$GITHUB_WORKSPACE/.errout" From cc25cab6a517a23fe69bd0a45abcc183ce6a3e6e Mon Sep 17 00:00:00 2001 From: Jeffrey Clark Date: Sun, 5 May 2024 12:34:58 -0500 Subject: [PATCH 3/4] update documentation with new volumes --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b2ee14..7a3022c 100644 --- a/README.md +++ b/README.md @@ -45,16 +45,23 @@ pushes all commits and tags back to this repo. Finally, Docker Hub is configured to watch the this repo and build/tag new images automatically based on the branch or tag that received new commits. + ## How to use the Image Download a release from Docker Hub as detailed above. e.g. ``` -docker pull puppet/pdk +docker pull puppet/pdk:latest ``` Run it ```bash -docker run -v /path/to/your/module:/root puppet/pdk +docker run -v /path/to/module:/workspace puppet/pdk +``` + +Run it with persistent pdk cache + +```bash +docker run -v /path/to/module:/workspace -v /path/to/cache:/cache puppet/pdk ``` From 836a52cc4212b92d905c60a92ae7ecf6400e30d8 Mon Sep 17 00:00:00 2001 From: Jeffrey Clark Date: Sun, 5 May 2024 20:35:34 -0500 Subject: [PATCH 4/4] update entrypoint to support a user specified workdir github action workflows needs this --- .github/workflows/ci.yml | 4 ++-- entrypoint.sh | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1ffc0a..220dd6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: echo '::error::expected an error that was not returned' exit 1 fi - grep -s 'error: /workspace in the container is not mounted' < "$GITHUB_WORKSPACE/.errout" + grep -s 'error: .* is not mounted in the container.' < "$GITHUB_WORKSPACE/.errout" - name: Test with a workspace volume run: | @@ -73,4 +73,4 @@ jobs: run: | cd roottest docker run --rm -v `pwd`:/root ${{ steps.build.outputs.imageid }} new class toor 2>"$GITHUB_WORKSPACE/.errout" - grep -s 'mounting a volume to /root in the container is deprecated' < "$GITHUB_WORKSPACE/.errout" + grep -s 'the /root workdir is deprecated' < "$GITHUB_WORKSPACE/.errout" diff --git a/entrypoint.sh b/entrypoint.sh index 09d3585..bb9ee4a 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,12 +1,12 @@ #!/bin/sh # re-entrant script to support automatically switching to an unprivileged user -# that matches the ownership of the RUN_VOLUME (see below) +# that matches the ownership of the RUN_WORKDIR (see below) set -e RUN_USER=pdk -RUN_VOLUME=/workspace +RUN_WORKDIR="${PWD}" [ -z "${UID}" ] && UID=$(id -u) [ -z "${GID}" ] && GID=$(id -g) @@ -16,10 +16,10 @@ RUN_VOLUME=/workspace # check if required path is mounted # check for deprecated /root volume if grep -sq " /root " < /proc/mounts ; then - [ -z "$ENTRYPOINT_RELOAD" ] && echo >&2 "mounting a volume to /root in the container is deprecated, use /workspace instead" - RUN_VOLUME=/root -elif ! grep -sq " ${RUN_VOLUME} " < /proc/mounts ; then - echo >&2 "error: ${RUN_VOLUME} in the container is not mounted." ; exit 1 + [ -z "$ENTRYPOINT_RELOAD" ] && echo >&2 "warning: the /root workdir is deprecated, use /workspace instead." + RUN_WORKDIR="/root" +elif ! grep -sq " ${RUN_WORKDIR} " < /proc/mounts ; then + echo >&2 "error: ${RUN_WORKDIR} is not mounted in the container." ; exit 1 fi create_user() { @@ -34,18 +34,18 @@ create_user() { # skip if re-running under newly created user if [ -z "$ENTRYPOINT_RELOAD" ] ; then if [ -z "$RUNNING_NON_ROOT" ] ; then - UID=$(stat -c '%u' "$RUN_VOLUME") - GID=$(stat -c '%g' "$RUN_VOLUME") + UID=$(stat -c '%u' "$RUN_WORKDIR") + GID=$(stat -c '%g' "$RUN_WORKDIR") [ "$UID" -eq 0 ] && RUN_USER="root" fi create_user "$UID" "$GID" # re-run with new user - exec su - $RUN_USER -c "cd $RUN_VOLUME ; ENTRYPOINT_RELOAD=1 $0 $*" + exec su - $RUN_USER -c "cd $RUN_WORKDIR ; ENTRYPOINT_RELOAD=1 $0 $*" exit fi # sanity check supported volumes -for volume in ${RUN_VOLUME} /cache ; do +for volume in ${RUN_WORKDIR} /cache ; do if [ ! -w "$volume" ] ; then echo >&2 "error: unable to write to ${volume}. Ensure permissions are correct on the host." ; exit 1 fi