From c43b959bf7c4786855dea218d9b40db2f0d6512e Mon Sep 17 00:00:00 2001 From: John Spaetzel Date: Mon, 12 Oct 2026 13:56:28 -0700 Subject: [PATCH 1/6] Run PHP SDK Docker builds as the host user on rootful Docker. Container root owns generated files on a normal Docker daemon, so a later checkout cannot replace them. Rootless Docker still runs as container root because that user is already the host user. Co-authored-by: Cursor --- .gitignore | 1 + bin/docker-user-args | 22 ++++++++++++++++++++++ bin/php | 10 ++++++---- sdks/php/bin/php | 10 ++++++---- sdks/php/bin/php-8 | 10 ++++++---- sdks/php/run-build | 5 +++++ 6 files changed, 46 insertions(+), 12 deletions(-) create mode 100755 bin/docker-user-args diff --git a/.gitignore b/.gitignore index 9417ee4db..858ed50c5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,6 @@ sdks/**/examples sdks/**/test_fixtures sdks/**/vendor vendor +.composer .idea .gradle \ No newline at end of file diff --git a/bin/docker-user-args b/bin/docker-user-args new file mode 100755 index 000000000..9a84f8674 --- /dev/null +++ b/bin/docker-user-args @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# Prints docker run arguments so a bind-mounted repo is not owned by root. +# Rootless Docker already maps container root to the host user, so it keeps +# --user root:root. Rootful Docker does not, and that is what leaves SDK files +# that a later git checkout cannot replace. Set DOCKER_RUN_AS_ROOT=1 to force +# the old behavior. + +set -euo pipefail + +if [[ "${DOCKER_RUN_AS_ROOT:-}" == "1" ]]; then + printf '%s\n' --user root:root + exit 0 +fi + +security="$(docker info --format '{{range .SecurityOptions}}{{println .}}{{end}}' 2>/dev/null || true)" +if grep -q rootless <<<"${security}"; then + printf '%s\n' --user root:root + exit 0 +fi + +printf '%s\n' --user "$(id -u):$(id -g)" diff --git a/bin/php b/bin/php index 3db44cde0..6b8551c11 100755 --- a/bin/php +++ b/bin/php @@ -1,7 +1,5 @@ #!/usr/bin/env bash -# Warning: You should run this from rootless Docker (or Podman, or other) - set -e DIR=$(cd `dirname $0` && pwd) @@ -13,9 +11,13 @@ if [[ -n "$GITHUB_ACTIONS" && -n "${DOCKER_USERNAME:-}" && -n "${DOCKER_TOKEN:-} echo "${DOCKER_TOKEN}" | docker login -u "${DOCKER_USERNAME}" --password-stdin fi +mapfile -t DOCKER_USER_ARGS < <("${DIR}/docker-user-args") +mkdir -p "${ROOT_DIR}/.composer" + docker run --rm \ + "${DOCKER_USER_ARGS[@]}" \ -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "dropbox-sign-sdk-composer-cache:/.composer" \ + -v "${ROOT_DIR}/.composer:/tmp/composer" \ + -e COMPOSER_HOME=/tmp/composer \ -w "${WORKING_DIR}" \ - -u root:root \ jtreminio/php-cli:7.4 "$@" diff --git a/sdks/php/bin/php b/sdks/php/bin/php index 9e31aee11..0da0ee0d5 100755 --- a/sdks/php/bin/php +++ b/sdks/php/bin/php @@ -1,7 +1,5 @@ #!/usr/bin/env bash -# Warning: You should run this from rootless Docker (or Podman, or other) - set -e DIR=$(cd `dirname $0` && pwd) @@ -9,12 +7,16 @@ ROOT_DIR="${DIR}/.." WORKING_DIR="/app/php" if [[ "$PHP_USE_NATIVE" -ne 1 ]]; then + mapfile -t DOCKER_USER_ARGS < <("${DIR}/../../../bin/docker-user-args") + mkdir -p "${ROOT_DIR}/.composer" + docker run --rm \ --platform linux/amd64 \ + "${DOCKER_USER_ARGS[@]}" \ -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "dropbox-sign-sdk-composer-cache:/.composer" \ + -v "${ROOT_DIR}/.composer:/tmp/composer" \ + -e COMPOSER_HOME=/tmp/composer \ -w "${WORKING_DIR}" \ - -u root:root \ jtreminio/php-cli:7.4 "$@" exit 0 diff --git a/sdks/php/bin/php-8 b/sdks/php/bin/php-8 index 147bb2b15..656bebebd 100755 --- a/sdks/php/bin/php-8 +++ b/sdks/php/bin/php-8 @@ -1,19 +1,21 @@ #!/usr/bin/env bash -# Warning: You should run this from rootless Docker (or Podman, or other) - set -e DIR=$(cd `dirname $0` && pwd) ROOT_DIR="${DIR}/.." WORKING_DIR="/app/php" +mapfile -t DOCKER_USER_ARGS < <("${DIR}/../../../bin/docker-user-args") +mkdir -p "${ROOT_DIR}/.composer" + docker run --rm \ --platform linux/amd64 \ + "${DOCKER_USER_ARGS[@]}" \ -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "dropbox-sign-sdk-composer-cache:/.composer" \ + -v "${ROOT_DIR}/.composer:/tmp/composer" \ + -e COMPOSER_HOME=/tmp/composer \ -w "${WORKING_DIR}" \ - -u root:root \ jtreminio/php:8.1 "$@" exit 0 diff --git a/sdks/php/run-build b/sdks/php/run-build index dd69c1ae1..d17477cc4 100755 --- a/sdks/php/run-build +++ b/sdks/php/run-build @@ -17,7 +17,10 @@ rm -f "${DIR}/composer.lock" rm -f "${DIR}/src/Api/"*.php rm -f "${DIR}/src/Model/"*.php +mapfile -t DOCKER_USER_ARGS < <("${DIR}/../../bin/docker-user-args") + docker run --rm \ + "${DOCKER_USER_ARGS[@]}" \ -v "${DIR}/:/local" \ -v "${DIR}/openapi-sdk.yaml:/local/openapi-sdk.yaml" \ openapitools/openapi-generator-cli:v7.12.0 generate \ @@ -39,12 +42,14 @@ bash "${DIR}/bin/php-8" ./vendor/bin/php-cs-fixer fix --verbose printf "Replacing strings ...\n" docker run --rm \ + "${DOCKER_USER_ARGS[@]}" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/replace printf "Scanning for prohibited keywords ...\n" docker run --rm \ + "${DOCKER_USER_ARGS[@]}" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/scan_for From 0bf3f8c0cd776ba7b56d83ae69043d5814c9257d Mon Sep 17 00:00:00 2001 From: John Spaetzel Date: Mon, 12 Oct 2026 14:15:09 -0700 Subject: [PATCH 2/6] Run every SDK Docker build as the host user on rootful Docker. Shared docker-run and php-in-docker wrappers replace the copied root invocations, and package caches live in host .docker directories so a non-root user can write them. Co-authored-by: Cursor --- .gitignore | 1 + bin/docker-run | 11 +++++++++++ bin/normalize-sdk-modes | 18 ++++++++++++++++++ bin/php | 18 ++++-------------- bin/php-in-docker | 29 +++++++++++++++++++++++++++++ bin/sandbox-dotnet | 20 +++++++++++++------- bin/sandbox-java | 13 +++++++------ bin/sandbox-node | 41 ++++++++++++++++++----------------------- bin/sandbox-ruby | 28 +++++++++++++++------------- sdks/dotnet/bin/dotnet | 15 +++++++++------ sdks/dotnet/bin/php | 14 +++----------- sdks/dotnet/run-build | 20 +++++++++----------- sdks/java-v1/bin/gradle | 15 +++++++++------ sdks/java-v1/bin/php | 14 +++----------- sdks/java-v1/run-build | 20 +++++++++----------- sdks/java-v2/bin/gradle | 15 +++++++++------ sdks/java-v2/bin/php | 14 +++----------- sdks/java-v2/run-build | 20 +++++++++----------- sdks/node/bin/node | 15 ++++++++++----- sdks/node/bin/php | 14 +++----------- sdks/node/run-build | 33 ++++++++++++++------------------- sdks/php/bin/php | 25 ++++++------------------- sdks/php/bin/php-8 | 20 ++++---------------- sdks/php/run-build | 25 +++++++++---------------- sdks/python/bin/php | 14 +++----------- sdks/python/bin/python | 18 ++++++++++++------ sdks/python/run-build | 20 +++++++++----------- sdks/ruby/bin/php | 14 +++----------- sdks/ruby/bin/ruby | 19 +++++++++++-------- sdks/ruby/run-build | 20 +++++++++----------- 30 files changed, 272 insertions(+), 291 deletions(-) create mode 100755 bin/docker-run create mode 100755 bin/normalize-sdk-modes create mode 100755 bin/php-in-docker diff --git a/.gitignore b/.gitignore index 858ed50c5..87f36fc86 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ sdks/**/test_fixtures sdks/**/vendor vendor .composer +.docker .idea .gradle \ No newline at end of file diff --git a/bin/docker-run b/bin/docker-run new file mode 100755 index 000000000..8b824bd53 --- /dev/null +++ b/bin/docker-run @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# SDK builds bind-mount the checkout. The user flag comes from docker-user-args +# so rootful Docker does not leave root-owned files behind. + +set -euo pipefail + +DIR=$(cd "$(dirname "$0")" && pwd) +mapfile -t DOCKER_USER_ARGS < <("${DIR}/docker-user-args") + +exec docker run --rm "${DOCKER_USER_ARGS[@]}" "$@" diff --git a/bin/normalize-sdk-modes b/bin/normalize-sdk-modes new file mode 100755 index 000000000..62fbaef77 --- /dev/null +++ b/bin/normalize-sdk-modes @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# openapi-generator's umask marks some generated sources executable. The SDK +# trees keep those files at 644. GitHub Actions owns the workspace already. + +set -euo pipefail + +if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + exit 0 +fi + +for path in "$@"; do + if [[ -f "${path}" ]]; then + chmod 644 "${path}" + elif [[ -d "${path}" ]]; then + find "${path}" -type f -exec chmod 644 {} \; + fi +done diff --git a/bin/php b/bin/php index 6b8551c11..603e5eda0 100755 --- a/bin/php +++ b/bin/php @@ -1,23 +1,13 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." -WORKING_DIR="/app/openapi" -if [[ -n "$GITHUB_ACTIONS" && -n "${DOCKER_USERNAME:-}" && -n "${DOCKER_TOKEN:-}" ]]; then +if [[ -n "${GITHUB_ACTIONS:-}" && -n "${DOCKER_USERNAME:-}" && -n "${DOCKER_TOKEN:-}" ]]; then printf "\nLogging in to docker.com ...\n" echo "${DOCKER_TOKEN}" | docker login -u "${DOCKER_USERNAME}" --password-stdin fi -mapfile -t DOCKER_USER_ARGS < <("${DIR}/docker-user-args") -mkdir -p "${ROOT_DIR}/.composer" - -docker run --rm \ - "${DOCKER_USER_ARGS[@]}" \ - -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "${ROOT_DIR}/.composer:/tmp/composer" \ - -e COMPOSER_HOME=/tmp/composer \ - -w "${WORKING_DIR}" \ - jtreminio/php-cli:7.4 "$@" +exec "${DIR}/php-in-docker" /app/openapi "${ROOT_DIR}" jtreminio/php-cli:7.4 "$@" diff --git a/bin/php-in-docker b/bin/php-in-docker new file mode 100755 index 000000000..5bbe90912 --- /dev/null +++ b/bin/php-in-docker @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# Composer writes both the SDK tree and its cache. The cache lives on the host +# so the user selected by docker-run can write it. Set PHP_DOCKER_PLATFORM to +# pass --platform (the PHP SDK images are amd64). + +set -euo pipefail + +DIR=$(cd "$(dirname "$0")" && pwd) +WORKING_DIR="$1" +ROOT_DIR="$2" +IMAGE="$3" +shift 3 + +mkdir -p "${ROOT_DIR}/.docker/composer" + +platform=() +if [[ -n "${PHP_DOCKER_PLATFORM:-}" ]]; then + platform=(--platform "${PHP_DOCKER_PLATFORM}") +fi + +exec "${DIR}/docker-run" \ + "${platform[@]}" \ + -v "${ROOT_DIR}:${WORKING_DIR}" \ + -v "${ROOT_DIR}/.docker/composer:/tmp/composer" \ + -e COMPOSER_HOME=/tmp/composer \ + -w "${WORKING_DIR}" \ + "${IMAGE}" \ + "$@" diff --git a/bin/sandbox-dotnet b/bin/sandbox-dotnet index c74759887..d5b207095 100755 --- a/bin/sandbox-dotnet +++ b/bin/sandbox-dotnet @@ -1,29 +1,35 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) WORKING_DIR="/app/openapi" SDK_DIR="${DIR}/../sdks/dotnet" TARGET_DIR="${DIR}/../sandbox/dotnet" ARTIFACTS_DIR="${DIR}/../sandbox/dotnet/artifacts" -mkdir -p "${ARTIFACTS_DIR}" +mkdir -p "${ARTIFACTS_DIR}" "${SDK_DIR}/.docker/nuget" "${SDK_DIR}/.docker/home" rm -f "${ARTIFACTS_DIR}/*.nupkg" rm -f "${TARGET_DIR}/src/Dropbox.SignSandbox/*.cs" cp "${SDK_DIR}/templates/Entry.cs" "${TARGET_DIR}/src/Dropbox.SignSandbox/Entry.cs" -docker run --rm -it \ +"${DIR}/docker-run" -it \ -v "${SDK_DIR}:${WORKING_DIR}" \ -v "${ARTIFACTS_DIR}:/artifacts" \ -v "${TARGET_DIR}:/target" \ + -v "${SDK_DIR}/.docker/home:/tmp/home" \ + -v "${SDK_DIR}/.docker/nuget:/tmp/nuget" \ + -e HOME=/tmp/home \ + -e NUGET_PACKAGES=/tmp/nuget \ -w "${WORKING_DIR}" \ - -u root:root \ mcr.microsoft.com/dotnet/sdk:9.0 dotnet pack -o /artifacts -docker run --rm -it \ +"${DIR}/docker-run" -it \ -v "${TARGET_DIR}:${WORKING_DIR}" \ + -v "${SDK_DIR}/.docker/home:/tmp/home" \ + -v "${SDK_DIR}/.docker/nuget:/tmp/nuget" \ + -e HOME=/tmp/home \ + -e NUGET_PACKAGES=/tmp/nuget \ -w "${WORKING_DIR}" \ - -u root:root \ mcr.microsoft.com/dotnet/sdk:9.0 dotnet build diff --git a/bin/sandbox-java b/bin/sandbox-java index 6f86eb5ac..bb7c39809 100755 --- a/bin/sandbox-java +++ b/bin/sandbox-java @@ -1,24 +1,25 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) WORKING_DIR="/app/openapi" SDK_DIR="${DIR}/../sdks/java-v2" TARGET_DIR="${DIR}/../sandbox/java" ARTIFACTS_DIR="${DIR}/../sandbox/java/artifacts" -mkdir -p "${ARTIFACTS_DIR}" +mkdir -p "${ARTIFACTS_DIR}" "${SDK_DIR}/.docker/gradle" rm -f "${ARTIFACTS_DIR}/*.jar" rm -f "${TARGET_DIR}/src/main/java/com/dropbox/sign_sandbox/*.java" cp "${SDK_DIR}/templates/Main.java" "${TARGET_DIR}/src/main/java/com/dropbox/sign_sandbox/Main.java" -docker run --rm -it \ +"${DIR}/docker-run" -it \ -v "${SDK_DIR}:${WORKING_DIR}" \ -v "${ARTIFACTS_DIR}:/artifacts" \ - -v "dropbox-sign-sdk-gradle-cache:/home/gradle/.gradle" \ - -v "dropbox-sign-sdk-maven-cache:/root/.m2" \ + -v "${SDK_DIR}/.docker/gradle:/tmp/gradle-user-home" \ + -e GRADLE_USER_HOME=/tmp/gradle-user-home \ + -e HOME=/tmp/gradle-user-home \ -w "${WORKING_DIR}" \ -e GEN_DIR=/app \ gradle:7.6.1-jdk11 ./gradlew clean fatJar diff --git a/bin/sandbox-node b/bin/sandbox-node index 60da86c08..c3a3274b3 100755 --- a/bin/sandbox-node +++ b/bin/sandbox-node @@ -1,40 +1,35 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) WORKING_DIR="/app/openapi" SDK_DIR="${DIR}/../sdks/node" TARGET_DIR="${DIR}/../sandbox/node" ARTIFACTS_DIR="${DIR}/../sandbox/node/artifacts" -mkdir -p "${ARTIFACTS_DIR}" +mkdir -p "${ARTIFACTS_DIR}" "${SDK_DIR}/.docker/npm" "${SDK_DIR}/.docker/home" rm -f "${ARTIFACTS_DIR}/*.tgz" rm -f "${TARGET_DIR}/src/*.ts" rm -f "${TARGET_DIR}/src/*.js" rm -f "${TARGET_DIR}/package-lock.json" rm -rf "${TARGET_DIR}/node_modules" -docker run -it --rm \ - -v "${SDK_DIR}:${WORKING_DIR}" \ - -v "${ARTIFACTS_DIR}:/artifacts" \ - -v "dropbox-sign-sdk-npm-cache:/root/.npm" \ - -w "${WORKING_DIR}" \ - node:22 npm install - -docker run -it --rm \ - -v "${SDK_DIR}:${WORKING_DIR}" \ - -v "${ARTIFACTS_DIR}:/artifacts" \ - -v "dropbox-sign-sdk-npm-cache:/root/.npm" \ - -w "${WORKING_DIR}" \ - node:22 npm run build - -docker run -it --rm \ - -v "${SDK_DIR}:${WORKING_DIR}" \ - -v "${ARTIFACTS_DIR}:/artifacts" \ - -v "dropbox-sign-sdk-npm-cache:/root/.npm" \ - -w "${WORKING_DIR}" \ - node:22 npm pack --pack-destination /artifacts +node_run() { + "${DIR}/docker-run" -it \ + -v "${SDK_DIR}:${WORKING_DIR}" \ + -v "${ARTIFACTS_DIR}:/artifacts" \ + -v "${SDK_DIR}/.docker/npm:/tmp/npm-cache" \ + -v "${SDK_DIR}/.docker/home:/tmp/home" \ + -e HOME=/tmp/home \ + -e npm_config_cache=/tmp/npm-cache \ + -w "${WORKING_DIR}" \ + node:22 "$@" +} + +node_run npm install +node_run npm run build +node_run npm pack --pack-destination /artifacts mv "${ARTIFACTS_DIR}/"*.tgz "${ARTIFACTS_DIR}/dropbox-sign-sdk.tgz" diff --git a/bin/sandbox-ruby b/bin/sandbox-ruby index 8abb45aa6..ddc43c659 100755 --- a/bin/sandbox-ruby +++ b/bin/sandbox-ruby @@ -1,8 +1,8 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) WORKING_DIR="/app/openapi" SDK_DIR="${DIR}/../sdks/ruby" @@ -10,23 +10,25 @@ TARGET_DIR="${DIR}/../sandbox/ruby" ARTIFACTS_DIR="${DIR}/../sandbox/ruby/artifacts" rm -rf "${ARTIFACTS_DIR}" -mkdir -p "${ARTIFACTS_DIR}/gems" +mkdir -p "${ARTIFACTS_DIR}/gems" "${SDK_DIR}/.docker/gem" "${SDK_DIR}/.docker/bundle" -docker run --rm \ - -e GEM_HOME="/.gem-cache" \ - -e BUNDLE_PATH="/.bundle-cache" \ +"${DIR}/docker-run" \ + -e GEM_HOME=/tmp/gem \ + -e BUNDLE_PATH=/tmp/bundle \ + -e HOME=/tmp/gem \ -v "${SDK_DIR}:${WORKING_DIR}" \ -v "${ARTIFACTS_DIR}:/artifacts" \ - -v "dropbox-sign-sdk-gem-cache:/.gem-cache" \ - -v "dropbox-sign-sdk-bundle-cache:/.bundle-cache" \ + -v "${SDK_DIR}/.docker/gem:/tmp/gem" \ + -v "${SDK_DIR}/.docker/bundle:/tmp/bundle" \ -w "${WORKING_DIR}" \ ruby:3.4 gem build dropbox-sign.gemspec -o /artifacts/gems/dropbox-sign.gem -docker run --rm \ - -e GEM_HOME="/.gem-cache" \ - -e BUNDLE_PATH="/.bundle-cache" \ +"${DIR}/docker-run" \ + -e GEM_HOME=/tmp/gem \ + -e BUNDLE_PATH=/tmp/bundle \ + -e HOME=/tmp/gem \ -v "${ARTIFACTS_DIR}:/artifacts" \ - -v "dropbox-sign-sdk-gem-cache:/.gem-cache" \ - -v "dropbox-sign-sdk-bundle-cache:/.bundle-cache" \ + -v "${SDK_DIR}/.docker/gem:/tmp/gem" \ + -v "${SDK_DIR}/.docker/bundle:/tmp/bundle" \ -w "/artifacts" \ ruby:3.4 gem generate_index diff --git a/sdks/dotnet/bin/dotnet b/sdks/dotnet/bin/dotnet index b3e5c445d..7ff9c3ab3 100755 --- a/sdks/dotnet/bin/dotnet +++ b/sdks/dotnet/bin/dotnet @@ -1,15 +1,18 @@ #!/usr/bin/env bash -# Warning: You should run this from rootless Docker (or Podman, or other) +set -euo pipefail -set -e - -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." WORKING_DIR="/app/dotnet" -docker run --rm \ +mkdir -p "${ROOT_DIR}/.docker/nuget" "${ROOT_DIR}/.docker/home" + +exec "${DIR}/../../../bin/docker-run" \ -v "${ROOT_DIR}:${WORKING_DIR}" \ + -v "${ROOT_DIR}/.docker/home:/tmp/home" \ + -v "${ROOT_DIR}/.docker/nuget:/tmp/nuget" \ + -e HOME=/tmp/home \ + -e NUGET_PACKAGES=/tmp/nuget \ -w "${WORKING_DIR}" \ - -u root:root \ mcr.microsoft.com/dotnet/sdk:8.0 "$@" diff --git a/sdks/dotnet/bin/php b/sdks/dotnet/bin/php index 806c7318f..ac7bf48e2 100755 --- a/sdks/dotnet/bin/php +++ b/sdks/dotnet/bin/php @@ -1,16 +1,8 @@ #!/usr/bin/env bash -# Warning: You should run this from rootless Docker (or Podman, or other) +set -euo pipefail -set -e - -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." -WORKING_DIR="/app/dotnet" -docker run --rm \ - -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "dropbox-sign-sdk-composer-cache:/.composer" \ - -w "${WORKING_DIR}" \ - -u root:root \ - jtreminio/php-cli:7.4 "$@" +exec "${DIR}/../../../bin/php-in-docker" /app/dotnet "${ROOT_DIR}" jtreminio/php-cli:7.4 "$@" diff --git a/sdks/dotnet/run-build b/sdks/dotnet/run-build index c4dff86f5..423af2086 100755 --- a/sdks/dotnet/run-build +++ b/sdks/dotnet/run-build @@ -16,7 +16,7 @@ fi rm -f "${DIR}/src/Dropbox.Sign/Api/"*.cs rm -f "${DIR}/src/Dropbox.Sign/Model/"*.cs -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}/:/local" \ openapitools/openapi-generator-cli:v7.12.0 generate \ -i "/local/openapi-sdk.yaml" \ @@ -35,13 +35,13 @@ printf "Adding examples to Docs ...\n" bash "${DIR}/bin/php" ./bin/generate-examples.php printf "Replacing strings ...\n" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/replace printf "Scanning for prohibited keywords ...\n" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/scan_for @@ -49,14 +49,12 @@ docker run --rm \ printf "Adding old-style constant names ...\n" bash "${DIR}/bin/php" ./bin/copy-constants.php -# avoid docker messing with permissions -if [[ -z "$GITHUB_ACTIONS" ]]; then - chmod 644 "${DIR}/README.md" - find "${DIR}/docs/" -type f -exec chmod 644 {} \; - find "${DIR}/examples/" -type f -exec chmod 644 {} \; - find "${DIR}/test_fixtures/" -type f -exec chmod 644 {} \; - find "${DIR}/src/" -type f -exec chmod 644 {} \; -fi +"${DIR}/../../bin/normalize-sdk-modes" \ + "${DIR}/README.md" \ + "${DIR}/docs" \ + "${DIR}/examples" \ + "${DIR}/test_fixtures" \ + "${DIR}/src" printf "Running tests ...\n" bash "${DIR}/bin/dotnet" dotnet test src/Dropbox.Sign.Test/ diff --git a/sdks/java-v1/bin/gradle b/sdks/java-v1/bin/gradle index 33882f688..3d127add3 100755 --- a/sdks/java-v1/bin/gradle +++ b/sdks/java-v1/bin/gradle @@ -2,16 +2,19 @@ # You probably want to run ./bin/gradle ./gradlew clean build -set -e +set -euo pipefail -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." WORKING_DIR="/app/java" -docker run --rm \ +mkdir -p "${ROOT_DIR}/.docker/gradle" + +exec "${DIR}/../../../bin/docker-run" \ -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "dropbox-sign-sdk-v1-gradle-cache:/home/gradle/.gradle" \ - -v "dropbox-sign-sdk-v1-maven-cache:/root/.m2" \ - -w "${WORKING_DIR}" \ + -v "${ROOT_DIR}/.docker/gradle:/tmp/gradle-user-home" \ + -e GRADLE_USER_HOME=/tmp/gradle-user-home \ + -e HOME=/tmp/gradle-user-home \ -e GEN_DIR=/app \ + -w "${WORKING_DIR}" \ gradle:7.6.1-jdk11 "$@" diff --git a/sdks/java-v1/bin/php b/sdks/java-v1/bin/php index 5785068b7..e75161bbe 100755 --- a/sdks/java-v1/bin/php +++ b/sdks/java-v1/bin/php @@ -1,16 +1,8 @@ #!/usr/bin/env bash -# Warning: You should run this from rootless Docker (or Podman, or other) +set -euo pipefail -set -e - -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." -WORKING_DIR="/app/java" -docker run --rm \ - -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "dropbox-sign-sdk-composer-cache:/.composer" \ - -w "${WORKING_DIR}" \ - -u root:root \ - jtreminio/php-cli:7.4 "$@" +exec "${DIR}/../../../bin/php-in-docker" /app/java "${ROOT_DIR}" jtreminio/php-cli:7.4 "$@" diff --git a/sdks/java-v1/run-build b/sdks/java-v1/run-build index 2262ad532..280346bf9 100755 --- a/sdks/java-v1/run-build +++ b/sdks/java-v1/run-build @@ -16,7 +16,7 @@ fi rm -f "${DIR}/src/main/java/com/dropbox/sign/api/"*.java rm -f "${DIR}/src/main/java/com/dropbox/sign/model/"*.java -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}/:/local" \ openapitools/openapi-generator-cli:v7.12.0 generate \ -i "/local/openapi-sdk.yaml" \ @@ -33,7 +33,7 @@ printf "Adding examples to Docs ...\n" bash "${DIR}/bin/php" ./bin/generate-examples.php printf "Replacing strings ...\n" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/replace @@ -42,7 +42,7 @@ printf "Generating method overloads ...\n" bash "${DIR}/bin/php" ./bin/generate-overload-methods.php printf "Scanning for prohibited keywords ...\n" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/scan_for @@ -50,14 +50,12 @@ docker run --rm \ printf "Adding old-style constant names ...\n" bash "${DIR}/bin/php" ./bin/copy-constants.php -# avoid docker messing with permissions -if [[ -z "$GITHUB_ACTIONS" ]]; then - chmod 644 "${DIR}/README.md" - find "${DIR}/docs/" -type f -exec chmod 644 {} \; - find "${DIR}/examples/" -type f -exec chmod 644 {} \; - find "${DIR}/test_fixtures/" -type f -exec chmod 644 {} \; - find "${DIR}/src/" -type f -exec chmod 644 {} \; -fi +"${DIR}/../../bin/normalize-sdk-modes" \ + "${DIR}/README.md" \ + "${DIR}/docs" \ + "${DIR}/examples" \ + "${DIR}/test_fixtures" \ + "${DIR}/src" # Remove empty directory rm -rf "${DIR}/api" diff --git a/sdks/java-v2/bin/gradle b/sdks/java-v2/bin/gradle index 98ba3ac9e..3d127add3 100755 --- a/sdks/java-v2/bin/gradle +++ b/sdks/java-v2/bin/gradle @@ -2,16 +2,19 @@ # You probably want to run ./bin/gradle ./gradlew clean build -set -e +set -euo pipefail -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." WORKING_DIR="/app/java" -docker run --rm \ +mkdir -p "${ROOT_DIR}/.docker/gradle" + +exec "${DIR}/../../../bin/docker-run" \ -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "dropbox-sign-sdk-v2-gradle-cache:/home/gradle/.gradle" \ - -v "dropbox-sign-sdk-v2-maven-cache:/root/.m2" \ - -w "${WORKING_DIR}" \ + -v "${ROOT_DIR}/.docker/gradle:/tmp/gradle-user-home" \ + -e GRADLE_USER_HOME=/tmp/gradle-user-home \ + -e HOME=/tmp/gradle-user-home \ -e GEN_DIR=/app \ + -w "${WORKING_DIR}" \ gradle:7.6.1-jdk11 "$@" diff --git a/sdks/java-v2/bin/php b/sdks/java-v2/bin/php index 5785068b7..e75161bbe 100755 --- a/sdks/java-v2/bin/php +++ b/sdks/java-v2/bin/php @@ -1,16 +1,8 @@ #!/usr/bin/env bash -# Warning: You should run this from rootless Docker (or Podman, or other) +set -euo pipefail -set -e - -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." -WORKING_DIR="/app/java" -docker run --rm \ - -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "dropbox-sign-sdk-composer-cache:/.composer" \ - -w "${WORKING_DIR}" \ - -u root:root \ - jtreminio/php-cli:7.4 "$@" +exec "${DIR}/../../../bin/php-in-docker" /app/java "${ROOT_DIR}" jtreminio/php-cli:7.4 "$@" diff --git a/sdks/java-v2/run-build b/sdks/java-v2/run-build index aae70c35a..9ec6fe17b 100755 --- a/sdks/java-v2/run-build +++ b/sdks/java-v2/run-build @@ -16,7 +16,7 @@ fi rm -f "${DIR}/src/main/java/com/dropbox/sign/api/"*.java rm -f "${DIR}/src/main/java/com/dropbox/sign/model/"*.java -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}/:/local" \ openapitools/openapi-generator-cli:v7.12.0 generate \ -i "/local/openapi-sdk.yaml" \ @@ -33,7 +33,7 @@ printf "Adding examples to Docs ...\n" bash "${DIR}/bin/php" ./bin/generate-examples.php printf "Replacing strings ...\n" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/replace @@ -42,7 +42,7 @@ printf "Generating method overloads ...\n" bash "${DIR}/bin/php" ./bin/generate-overload-methods.php printf "Scanning for prohibited keywords ...\n" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/scan_for @@ -50,14 +50,12 @@ docker run --rm \ printf "Adding old-style constant names ...\n" bash "${DIR}/bin/php" ./bin/copy-constants.php -# avoid docker messing with permissions -if [[ -z "$GITHUB_ACTIONS" ]]; then - chmod 644 "${DIR}/README.md" - find "${DIR}/docs/" -type f -exec chmod 644 {} \; - find "${DIR}/examples/" -type f -exec chmod 644 {} \; - find "${DIR}/test_fixtures/" -type f -exec chmod 644 {} \; - find "${DIR}/src/" -type f -exec chmod 644 {} \; -fi +"${DIR}/../../bin/normalize-sdk-modes" \ + "${DIR}/README.md" \ + "${DIR}/docs" \ + "${DIR}/examples" \ + "${DIR}/test_fixtures" \ + "${DIR}/src" # Remove empty directory rm -rf "${DIR}/api" diff --git a/sdks/node/bin/node b/sdks/node/bin/node index 6badd28c3..fb44e24ca 100755 --- a/sdks/node/bin/node +++ b/sdks/node/bin/node @@ -1,13 +1,18 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." -docker run --rm \ +mkdir -p "${ROOT_DIR}/.docker/npm" "${ROOT_DIR}/.docker/home" "${ROOT_DIR}/node_modules" + +exec "${DIR}/../../../bin/docker-run" \ -v "${ROOT_DIR}:/app" \ - -v "dropbox-sign-sdk-npm-cache:/root/.npm" \ - -v "dropbox-sign-sdk-node-modules:/app/node_modules" \ + -v "${ROOT_DIR}/node_modules:/app/node_modules" \ + -v "${ROOT_DIR}/.docker/npm:/tmp/npm-cache" \ + -v "${ROOT_DIR}/.docker/home:/tmp/home" \ + -e HOME=/tmp/home \ + -e npm_config_cache=/tmp/npm-cache \ -w /app \ node:18 "$@" diff --git a/sdks/node/bin/php b/sdks/node/bin/php index afb80b1cb..8451252e8 100755 --- a/sdks/node/bin/php +++ b/sdks/node/bin/php @@ -1,16 +1,8 @@ #!/usr/bin/env bash -# Warning: You should run this from rootless Docker (or Podman, or other) +set -euo pipefail -set -e - -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." -WORKING_DIR="/app/javascript" -docker run --rm \ - -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "dropbox-sign-sdk-composer-cache:/.composer" \ - -w "${WORKING_DIR}" \ - -u root:root \ - jtreminio/php-cli:7.4 "$@" +exec "${DIR}/../../../bin/php-in-docker" /app/javascript "${ROOT_DIR}" jtreminio/php-cli:7.4 "$@" diff --git a/sdks/node/run-build b/sdks/node/run-build index 574cbc0da..c4d10ba21 100755 --- a/sdks/node/run-build +++ b/sdks/node/run-build @@ -19,9 +19,8 @@ rm -f "${DIR}/types/api/"*.ts rm -f "${DIR}/types/model/"*.ts # Generate code -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}/:/local" \ - --user "$(id -u):$(id -g)" \ openapitools/openapi-generator-cli:v7.12.0 generate \ -i "/local/openapi-sdk.yaml" \ -c "/local/openapi-config.yaml" \ @@ -36,9 +35,8 @@ docker run --rm \ rm -rf "${DIR}/docs/api" "${DIR}/tmp_docs" mkdir -p "${DIR}/docs/api" mkdir -p "${DIR}/tmp_docs" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}/:/local" \ - --user "$(id -u):$(id -g)" \ openapitools/openapi-generator-cli:v7.8.0 generate \ -i "/local/openapi-sdk.yaml" \ -c "/local/openapi-config.yaml" \ @@ -57,9 +55,8 @@ mv "${DIR}/tmp_docs/"*.md "${DIR}/docs/api/" rm -rf "${DIR}/docs/model" "${DIR}/tmp_docs" mkdir -p "${DIR}/docs/model" mkdir -p "${DIR}/tmp_docs" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}/:/local" \ - --user "$(id -u):$(id -g)" \ openapitools/openapi-generator-cli:v7.8.0 generate \ -i "/local/openapi-sdk.yaml" \ -c "/local/openapi-config.yaml" \ @@ -91,13 +88,13 @@ printf "Running prettier ...\n" bash "${DIR}/bin/node" npm run prettier printf "Replacing strings ...\n" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/replace printf "Scanning for prohibited keywords ...\n" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/scan_for @@ -109,17 +106,15 @@ printf "Running build ...\n" bash "${DIR}/bin/node" npm run build bash "${DIR}/bin/node" npm run build-types -# avoid docker messing with permissions -if [[ -z "$GITHUB_ACTIONS" ]]; then - chmod 644 "${DIR}/README.md" - find "${DIR}/docs/" -type f -exec chmod 644 {} \; - find "${DIR}/examples/" -type f -exec chmod 644 {} \; - find "${DIR}/test_fixtures/" -type f -exec chmod 644 {} \; - find "${DIR}/api/" -type f -exec chmod 644 {} \; - find "${DIR}/dist/" -type f -exec chmod 644 {} \; - find "${DIR}/model/" -type f -exec chmod 644 {} \; - find "${DIR}/types/" -type f -exec chmod 644 {} \; -fi +"${DIR}/../../bin/normalize-sdk-modes" \ + "${DIR}/README.md" \ + "${DIR}/docs" \ + "${DIR}/examples" \ + "${DIR}/test_fixtures" \ + "${DIR}/api" \ + "${DIR}/dist" \ + "${DIR}/model" \ + "${DIR}/types" printf "Running tests ...\n" bash "${DIR}/bin/node" npm run test diff --git a/sdks/php/bin/php b/sdks/php/bin/php index 0da0ee0d5..af16b6a73 100755 --- a/sdks/php/bin/php +++ b/sdks/php/bin/php @@ -1,27 +1,14 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." -WORKING_DIR="/app/php" -if [[ "$PHP_USE_NATIVE" -ne 1 ]]; then - mapfile -t DOCKER_USER_ARGS < <("${DIR}/../../../bin/docker-user-args") - mkdir -p "${ROOT_DIR}/.composer" - - docker run --rm \ - --platform linux/amd64 \ - "${DOCKER_USER_ARGS[@]}" \ - -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "${ROOT_DIR}/.composer:/tmp/composer" \ - -e COMPOSER_HOME=/tmp/composer \ - -w "${WORKING_DIR}" \ - jtreminio/php-cli:7.4 "$@" - - exit 0 +if [[ "${PHP_USE_NATIVE:-0}" -ne 1 ]]; then + export PHP_DOCKER_PLATFORM=linux/amd64 + exec "${DIR}/../../../bin/php-in-docker" /app/php "${ROOT_DIR}" jtreminio/php-cli:7.4 "$@" fi -pushd "${ROOT_DIR}" +cd "${ROOT_DIR}" exec "$@" -popd diff --git a/sdks/php/bin/php-8 b/sdks/php/bin/php-8 index 656bebebd..210061460 100755 --- a/sdks/php/bin/php-8 +++ b/sdks/php/bin/php-8 @@ -1,21 +1,9 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." -WORKING_DIR="/app/php" -mapfile -t DOCKER_USER_ARGS < <("${DIR}/../../../bin/docker-user-args") -mkdir -p "${ROOT_DIR}/.composer" - -docker run --rm \ - --platform linux/amd64 \ - "${DOCKER_USER_ARGS[@]}" \ - -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "${ROOT_DIR}/.composer:/tmp/composer" \ - -e COMPOSER_HOME=/tmp/composer \ - -w "${WORKING_DIR}" \ - jtreminio/php:8.1 "$@" - - exit 0 +export PHP_DOCKER_PLATFORM=linux/amd64 +exec "${DIR}/../../../bin/php-in-docker" /app/php "${ROOT_DIR}" jtreminio/php:8.1 "$@" diff --git a/sdks/php/run-build b/sdks/php/run-build index d17477cc4..8b02e2d3a 100755 --- a/sdks/php/run-build +++ b/sdks/php/run-build @@ -17,10 +17,7 @@ rm -f "${DIR}/composer.lock" rm -f "${DIR}/src/Api/"*.php rm -f "${DIR}/src/Model/"*.php -mapfile -t DOCKER_USER_ARGS < <("${DIR}/../../bin/docker-user-args") - -docker run --rm \ - "${DOCKER_USER_ARGS[@]}" \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}/:/local" \ -v "${DIR}/openapi-sdk.yaml:/local/openapi-sdk.yaml" \ openapitools/openapi-generator-cli:v7.12.0 generate \ @@ -41,15 +38,13 @@ printf "Running php-cs-fixer ...\n" bash "${DIR}/bin/php-8" ./vendor/bin/php-cs-fixer fix --verbose printf "Replacing strings ...\n" -docker run --rm \ - "${DOCKER_USER_ARGS[@]}" \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/replace printf "Scanning for prohibited keywords ...\n" -docker run --rm \ - "${DOCKER_USER_ARGS[@]}" \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/scan_for @@ -57,14 +52,12 @@ docker run --rm \ printf "Adding old-style constant names ...\n" bash "${DIR}/bin/php-8" ./bin/copy-constants.php -# avoid docker messing with permissions -if [[ -z "$GITHUB_ACTIONS" ]]; then - chmod 644 "${DIR}/README.md" - find "${DIR}/docs/" -type f -exec chmod 644 {} \; - find "${DIR}/examples/" -type f -exec chmod 644 {} \; - find "${DIR}/test_fixtures/" -type f -exec chmod 644 {} \; - find "${DIR}/src/" -type f -exec chmod 644 {} \; -fi +"${DIR}/../../bin/normalize-sdk-modes" \ + "${DIR}/README.md" \ + "${DIR}/docs" \ + "${DIR}/examples" \ + "${DIR}/test_fixtures" \ + "${DIR}/src" printf "Running unit tests ...\n" PHP_USE_NATIVE=0 bash "${DIR}/bin/php" ./vendor/bin/phpunit diff --git a/sdks/python/bin/php b/sdks/python/bin/php index f59f0a528..57baa541d 100755 --- a/sdks/python/bin/php +++ b/sdks/python/bin/php @@ -1,16 +1,8 @@ #!/usr/bin/env bash -# Warning: You should run this from rootless Docker (or Podman, or other) +set -euo pipefail -set -e - -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." -WORKING_DIR="/app/python" -docker run --rm \ - -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "dropbox-sign-sdk-composer-cache:/.composer" \ - -w "${WORKING_DIR}" \ - -u root:root \ - jtreminio/php-cli:7.4 "$@" +exec "${DIR}/../../../bin/php-in-docker" /app/python "${ROOT_DIR}" jtreminio/php-cli:7.4 "$@" diff --git a/sdks/python/bin/python b/sdks/python/bin/python index 28cb66524..ffa2a282c 100755 --- a/sdks/python/bin/python +++ b/sdks/python/bin/python @@ -1,15 +1,21 @@ #!/usr/bin/env bash -# Warning: You should run this from rootless Docker (or Podman, or other) +# The old pip volume replaced /usr/local/lib/python3.10, which hid the +# interpreter on a fresh mount and was owned by root. A virtualenv under +# .docker is writable by the host user and leaves the image stdlib alone. -set -e +set -euo pipefail -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." WORKING_DIR="/app/python" -docker run --rm \ +mkdir -p "${ROOT_DIR}/.docker/venv" + +exec "${DIR}/../../../bin/docker-run" \ -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "dropbox-sign-sdk-pip-cache:/usr/local/lib/python3.10" \ + -v "${ROOT_DIR}/.docker/venv:/opt/venv" \ -w "${WORKING_DIR}" \ - python:3.10-buster "$@" + python:3.10-buster \ + bash -c 'if [[ ! -x /opt/venv/bin/python ]]; then python -m venv /opt/venv; fi; export PATH="/opt/venv/bin:${PATH}"; exec "$@"' \ + bash "$@" diff --git a/sdks/python/run-build b/sdks/python/run-build index 09284e82c..0d79dc5b1 100755 --- a/sdks/python/run-build +++ b/sdks/python/run-build @@ -16,7 +16,7 @@ fi rm -f "${DIR}/dropbox_sign/api/"*.py rm -f "${DIR}/dropbox_sign/models/"*.py -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}/:/local" \ openapitools/openapi-generator-cli:v7.12.0 generate \ -i "/local/openapi-sdk.yaml" \ @@ -39,23 +39,21 @@ printf "\n" printf "Adding examples to Docs ...\n" bash "${DIR}/bin/php" ./bin/generate-examples.php -# avoid docker messing with permissions -if [[ -z "$GITHUB_ACTIONS" ]]; then - chmod 644 "${DIR}/README.md" - find "${DIR}/docs/" -type f -exec chmod 644 {} \; - find "${DIR}/examples/" -type f -exec chmod 644 {} \; - find "${DIR}/test_fixtures/" -type f -exec chmod 644 {} \; - find "${DIR}/dropbox_sign/" -type f -exec chmod 644 {} \; -fi +"${DIR}/../../bin/normalize-sdk-modes" \ + "${DIR}/README.md" \ + "${DIR}/docs" \ + "${DIR}/examples" \ + "${DIR}/test_fixtures" \ + "${DIR}/dropbox_sign" printf "Replacing strings ...\n" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/replace printf "Scanning for prohibited keywords ...\n" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/scan_for diff --git a/sdks/ruby/bin/php b/sdks/ruby/bin/php index 9aba93115..37850ffd5 100755 --- a/sdks/ruby/bin/php +++ b/sdks/ruby/bin/php @@ -1,16 +1,8 @@ #!/usr/bin/env bash -# Warning: You should run this from rootless Docker (or Podman, or other) +set -euo pipefail -set -e - -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." -WORKING_DIR="/app/ruby" -docker run --rm \ - -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "dropbox-sign-sdk-composer-cache:/.composer" \ - -w "${WORKING_DIR}" \ - -u root:root \ - jtreminio/php-cli:7.4 "$@" +exec "${DIR}/../../../bin/php-in-docker" /app/ruby "${ROOT_DIR}" jtreminio/php-cli:7.4 "$@" diff --git a/sdks/ruby/bin/ruby b/sdks/ruby/bin/ruby index 9e2e1c7bf..2503388d4 100755 --- a/sdks/ruby/bin/ruby +++ b/sdks/ruby/bin/ruby @@ -1,17 +1,20 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -DIR=$(cd `dirname $0` && pwd) +DIR=$(cd "$(dirname "$0")" && pwd) ROOT_DIR="${DIR}/.." WORKING_DIR="/app/ruby" -docker run --rm \ - -e GEM_HOME="/.gem-cache" \ - -e BUNDLE_PATH="/.bundle-cache" \ - -e RUBYGEMS_API_KEY=${RUBYGEMS_API_KEY-:''} \ +mkdir -p "${ROOT_DIR}/.docker/gem" "${ROOT_DIR}/.docker/bundle" + +exec "${DIR}/../../../bin/docker-run" \ + -e GEM_HOME=/tmp/gem \ + -e BUNDLE_PATH=/tmp/bundle \ + -e HOME=/tmp/gem \ + -e RUBYGEMS_API_KEY="${RUBYGEMS_API_KEY-:''}" \ -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "dropbox-sign-sdk-gem-cache:/.gem-cache" \ - -v "dropbox-sign-sdk-bundle-cache:/.bundle-cache" \ + -v "${ROOT_DIR}/.docker/gem:/tmp/gem" \ + -v "${ROOT_DIR}/.docker/bundle:/tmp/bundle" \ -w "${WORKING_DIR}" \ ruby:3.1 "$@" diff --git a/sdks/ruby/run-build b/sdks/ruby/run-build index cecea48f1..2e64adb97 100755 --- a/sdks/ruby/run-build +++ b/sdks/ruby/run-build @@ -16,7 +16,7 @@ fi rm -f "${DIR}/lib/dropbox-sign/api/"*.rb rm -f "${DIR}/lib/dropbox-sign/models/"*.rb -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}/:/local" \ openapitools/openapi-generator-cli:v7.12.0 generate \ -i "/local/openapi-sdk.yaml" \ @@ -39,25 +39,23 @@ printf "Adding examples to Docs ...\n" bash "${DIR}/bin/php" ./bin/generate-examples.php printf "Replacing strings ...\n" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/replace printf "Scanning for prohibited keywords ...\n" -docker run --rm \ +"${DIR}/../../bin/docker-run" \ -v "${DIR}:${WORKING_DIR}" \ -w "${WORKING_DIR}" \ perl bash ./bin/scan_for -# avoid docker messing with permissions -if [[ -z "$GITHUB_ACTIONS" ]]; then - chmod 644 "${DIR}/README.md" - find "${DIR}/docs/" -type f -exec chmod 644 {} \; - find "${DIR}/examples/" -type f -exec chmod 644 {} \; - find "${DIR}/test_fixtures/" -type f -exec chmod 644 {} \; - find "${DIR}/lib/" -type f -exec chmod 644 {} \; -fi +"${DIR}/../../bin/normalize-sdk-modes" \ + "${DIR}/README.md" \ + "${DIR}/docs" \ + "${DIR}/examples" \ + "${DIR}/test_fixtures" \ + "${DIR}/lib" printf "Running tests ...\n" bash "${DIR}/bin/ruby" bundle exec rspec From 23bfd20652488f476aaa7b60c61aed7ddf9837b2 Mon Sep 17 00:00:00 2001 From: John Spaetzel Date: Wed, 4 Nov 2026 12:42:29 -0800 Subject: [PATCH 3/6] Check SDK Docker scripts on Bash 3.2 (#590) Co-authored-by: Cursor --- .github/workflows/bash32-script-compat.yml | 42 ++++ bin/check-bash32 | 258 +++++++++++++++++++++ bin/docker-run | 5 +- bin/php-in-docker | 21 +- 4 files changed, 315 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/bash32-script-compat.yml create mode 100755 bin/check-bash32 diff --git a/.github/workflows/bash32-script-compat.yml b/.github/workflows/bash32-script-compat.yml new file mode 100644 index 000000000..c6eab8ad2 --- /dev/null +++ b/.github/workflows/bash32-script-compat.yml @@ -0,0 +1,42 @@ +name: Bash 3.2 script compatibility + +on: + push: + branches: [ main ] + paths: &script-paths + - bin/check-bash32 + - bin/docker-run + - bin/docker-user-args + - bin/normalize-sdk-modes + - bin/php + - bin/php-in-docker + - bin/sandbox-dotnet + - bin/sandbox-java + - bin/sandbox-node + - bin/sandbox-ruby + - sdks/*/bin/dotnet + - sdks/*/bin/gradle + - sdks/*/bin/node + - sdks/*/bin/php + - sdks/*/bin/php-8 + - sdks/*/bin/python + - sdks/*/bin/ruby + - sdks/*/run-build + - .github/workflows/bash32-script-compat.yml + pull_request: + branches: [ main ] + paths: *script-paths + +permissions: + contents: read + +jobs: + bash32: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: Check SDK scripts on Bash 3.2 + run: ./bin/check-bash32 diff --git a/bin/check-bash32 b/bin/check-bash32 new file mode 100755 index 000000000..e469f9b1a --- /dev/null +++ b/bin/check-bash32 @@ -0,0 +1,258 @@ +#!/usr/bin/env bash + +# macOS ships Bash 3.2. SDK Docker wrappers must keep running there. +# This re-executes under the bash:3.2 image, parses the wrappers, rejects +# Bash 4 syntax that `bash -n` cannot see, and runs them with docker stubbed. + +set -euo pipefail + +ROOT=$(cd "$(dirname "$0")/.." && pwd) + +if [[ "${CHECK_BASH32_INNER:-}" != "1" ]]; then + exec docker run --rm \ + -v "${ROOT}:/work" \ + -w /work \ + -e CHECK_BASH32_INNER=1 \ + "${BASH32_IMAGE:-bash:3.2}" \ + bash bin/check-bash32 +fi + +if [[ "${BASH_VERSINFO[0]}" -ne 3 || "${BASH_VERSINFO[1]}" -ne 2 ]]; then + printf 'expected Bash 3.2, found %s\n' "${BASH_VERSION}" >&2 + exit 1 +fi + +# Bash 4+ tokens. `bash -n` accepts `mapfile` on Bash 3.2 because it is +# just a command name; the shell only fails when that line runs. +bash4_pattern='(^|[[:space:]])(mapfile|readarray|coproc)([[:space:]]|$)|declare[[:space:]]+-A|\[\[[[:space:]]+-v[[:space:]]|\$\{[A-Za-z_][A-Za-z0-9_]*(,,|\^\^)|\$\{[A-Za-z_][A-Za-z0-9_]*@[A-Za-z]|\$\{[^}]*\[-[0-9]+\]|&>>|[|][&]' + +scripts=( + bin/docker-run + bin/docker-user-args + bin/normalize-sdk-modes + bin/php + bin/php-in-docker + bin/sandbox-dotnet + bin/sandbox-java + bin/sandbox-node + bin/sandbox-ruby + sdks/dotnet/bin/dotnet + sdks/dotnet/bin/php + sdks/dotnet/run-build + sdks/java-v1/bin/gradle + sdks/java-v1/bin/php + sdks/java-v1/run-build + sdks/java-v2/bin/gradle + sdks/java-v2/bin/php + sdks/java-v2/run-build + sdks/node/bin/node + sdks/node/bin/php + sdks/node/run-build + sdks/php/bin/php + sdks/php/bin/php-8 + sdks/php/run-build + sdks/python/bin/php + sdks/python/bin/python + sdks/python/run-build + sdks/ruby/bin/php + sdks/ruby/bin/ruby + sdks/ruby/run-build +) + +reject_bash4() { + local script="$1" + local line + line=$(grep -nE "${bash4_pattern}" "${script}" | grep -vE '^[0-9]+:[[:space:]]*#' || true) + if [[ -n "${line}" ]]; then + printf 'Bash 4+ syntax in %s:\n%s\n' "${script}" "${line}" >&2 + return 1 + fi +} + +self_test_denylist() { + local tmp + tmp=$(mktemp) + printf '#!/usr/bin/env bash\n# mapfile stays in this comment\necho ok\n' > "${tmp}" + reject_bash4 "${tmp}" + printf '#!/usr/bin/env bash\nmapfile -t xs < /dev/null\n' > "${tmp}" + if reject_bash4 "${tmp}" 2>/dev/null; then + rm -f "${tmp}" + printf 'denylist did not reject mapfile\n' >&2 + exit 1 + fi + rm -f "${tmp}" +} + +assert_log_contains() { + local needle="$1" + if ! grep -Fxq -e "${needle}" "${DOCKER_RUN_LOG}"; then + printf 'expected docker argument <%s>\nactual:\n' "${needle}" >&2 + cat "${DOCKER_RUN_LOG}" >&2 + exit 1 + fi +} + +assert_log_excludes() { + local needle="$1" + if grep -Fxq -e "${needle}" "${DOCKER_RUN_LOG}"; then + printf 'unexpected docker argument <%s>\nactual:\n' "${needle}" >&2 + cat "${DOCKER_RUN_LOG}" >&2 + exit 1 + fi +} + +run_docker_wrapper() { + : > "${DOCKER_RUN_LOG}" + if ! "$@"; then + printf 'command failed:' >&2 + printf ' %s' "$@" >&2 + printf '\n' >&2 + exit 1 + fi +} + +self_test_denylist + +for script in "${scripts[@]}"; do + if ! bash -n "${script}"; then + printf 'Bash 3.2 rejected %s\n' "${script}" >&2 + exit 1 + fi + reject_bash4 "${script}" +done + +work=$(mktemp -d) +stubdir=$(mktemp -d) +trap 'rm -rf "${work}" "${stubdir}"' EXIT + +for script in "${scripts[@]}"; do + mkdir -p "${work}/$(dirname "${script}")" + cp "${script}" "${work}/${script}" +done + +cat > "${stubdir}/docker" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +if [[ "${1:-}" == "info" ]]; then + if [[ -n "${DOCKER_INFO_SECURITY:-}" ]]; then + printf '%s\n' "${DOCKER_INFO_SECURITY}" + fi + exit 0 +fi +if [[ "${1:-}" == "run" ]]; then + printf '%s\n' "$@" >> "${DOCKER_RUN_LOG:?}" + exit 0 +fi +printf 'unexpected docker command: %s\n' "$*" >&2 +exit 1 +EOF +chmod +x "${stubdir}/docker" + +export PATH="${stubdir}:${PATH}" +DOCKER_RUN_LOG=$(mktemp) +export DOCKER_RUN_LOG +unset DOCKER_RUN_AS_ROOT DOCKER_INFO_SECURITY PHP_DOCKER_PLATFORM PHP_USE_NATIVE GITHUB_ACTIONS + +host_user="$(id -u):$(id -g)" + +user_args=$("${work}/bin/docker-user-args") +expected=$(printf '%s\n' --user "${host_user}") +if [[ "${user_args}" != "${expected}" ]]; then + printf 'rootful docker-user-args returned:\n%s\n' "${user_args}" >&2 + exit 1 +fi + +user_args=$(DOCKER_RUN_AS_ROOT=1 "${work}/bin/docker-user-args") +expected=$(printf '%s\n' --user root:root) +if [[ "${user_args}" != "${expected}" ]]; then + printf 'DOCKER_RUN_AS_ROOT docker-user-args returned:\n%s\n' "${user_args}" >&2 + exit 1 +fi + +user_args=$(DOCKER_INFO_SECURITY=name=rootless "${work}/bin/docker-user-args") +if [[ "${user_args}" != "${expected}" ]]; then + printf 'rootless docker-user-args returned:\n%s\n' "${user_args}" >&2 + exit 1 +fi + +run_docker_wrapper "${work}/bin/docker-run" echo ok +assert_log_contains --rm +assert_log_contains --user +assert_log_contains "${host_user}" +assert_log_contains echo +assert_log_contains ok + +DOCKER_RUN_AS_ROOT=1 run_docker_wrapper "${work}/bin/docker-run" echo ok +assert_log_contains root:root + +runtime=( + bin/php + sdks/dotnet/bin/dotnet + sdks/dotnet/bin/php + sdks/java-v1/bin/gradle + sdks/java-v1/bin/php + sdks/java-v2/bin/gradle + sdks/java-v2/bin/php + sdks/node/bin/node + sdks/node/bin/php + sdks/php/bin/php + sdks/php/bin/php-8 + sdks/python/bin/php + sdks/python/bin/python + sdks/ruby/bin/php + sdks/ruby/bin/ruby +) + +for script in "${runtime[@]}"; do + run_docker_wrapper "${work}/${script}" true + assert_log_contains --user + assert_log_contains "${host_user}" + assert_log_contains true +done + +run_docker_wrapper "${work}/bin/php" composer install +assert_log_excludes --platform +assert_log_contains jtreminio/php-cli:7.4 + +run_docker_wrapper "${work}/sdks/node/bin/php" composer install +assert_log_excludes --platform + +run_docker_wrapper "${work}/sdks/php/bin/php" php -v +assert_log_contains --platform +assert_log_contains linux/amd64 +assert_log_contains jtreminio/php-cli:8.1 + +run_docker_wrapper "${work}/sdks/php/bin/php-8" php -v +assert_log_contains --platform +assert_log_contains jtreminio/php:8.1 + +run_docker_wrapper "${work}/sdks/dotnet/bin/dotnet" dotnet test +assert_log_contains mcr.microsoft.com/dotnet/sdk:10.0 + +: > "${DOCKER_RUN_LOG}" +PHP_USE_NATIVE=1 "${work}/sdks/php/bin/php" true +if [[ -s "${DOCKER_RUN_LOG}" ]]; then + printf 'PHP_USE_NATIVE=1 invoked docker:\n' >&2 + cat "${DOCKER_RUN_LOG}" >&2 + exit 1 +fi + +sample=$(mktemp) +printf 'x\n' > "${sample}" +chmod 755 "${sample}" +"${work}/bin/normalize-sdk-modes" "${sample}" +mode=$(stat -c %a "${sample}") +if [[ "${mode}" != "644" ]]; then + printf 'normalize-sdk-modes left mode %s\n' "${mode}" >&2 + exit 1 +fi +chmod 755 "${sample}" +GITHUB_ACTIONS=1 "${work}/bin/normalize-sdk-modes" "${sample}" +mode=$(stat -c %a "${sample}") +if [[ "${mode}" != "755" ]]; then + printf 'normalize-sdk-modes changed mode to %s on GitHub Actions\n' "${mode}" >&2 + exit 1 +fi +rm -f "${sample}" + +printf 'Bash %s accepted the SDK Docker scripts\n' "${BASH_VERSION}" diff --git a/bin/docker-run b/bin/docker-run index 8b824bd53..d7629112d 100755 --- a/bin/docker-run +++ b/bin/docker-run @@ -6,6 +6,9 @@ set -euo pipefail DIR=$(cd "$(dirname "$0")" && pwd) -mapfile -t DOCKER_USER_ARGS < <("${DIR}/docker-user-args") +DOCKER_USER_ARGS=() +while IFS= read -r arg; do + DOCKER_USER_ARGS+=("$arg") +done < <("${DIR}/docker-user-args") exec docker run --rm "${DOCKER_USER_ARGS[@]}" "$@" diff --git a/bin/php-in-docker b/bin/php-in-docker index 5bbe90912..de0916679 100755 --- a/bin/php-in-docker +++ b/bin/php-in-docker @@ -14,16 +14,17 @@ shift 3 mkdir -p "${ROOT_DIR}/.docker/composer" -platform=() +# Keep this array non-empty. Bash 3.2 with set -u errors on an empty +# "${array[@]}", and that is the Bash macOS ships. +docker_args=( + -v "${ROOT_DIR}:${WORKING_DIR}" + -v "${ROOT_DIR}/.docker/composer:/tmp/composer" + -e COMPOSER_HOME=/tmp/composer + -w "${WORKING_DIR}" + "${IMAGE}" +) if [[ -n "${PHP_DOCKER_PLATFORM:-}" ]]; then - platform=(--platform "${PHP_DOCKER_PLATFORM}") + docker_args=(--platform "${PHP_DOCKER_PLATFORM}" "${docker_args[@]}") fi -exec "${DIR}/docker-run" \ - "${platform[@]}" \ - -v "${ROOT_DIR}:${WORKING_DIR}" \ - -v "${ROOT_DIR}/.docker/composer:/tmp/composer" \ - -e COMPOSER_HOME=/tmp/composer \ - -w "${WORKING_DIR}" \ - "${IMAGE}" \ - "$@" +exec "${DIR}/docker-run" "${docker_args[@]}" "$@" From 94ecdd81e5a2eec438c1f8cd47eec9267798337e Mon Sep 17 00:00:00 2001 From: John Spaetzel Date: Wed, 4 Nov 2026 12:45:00 -0800 Subject: [PATCH 4/6] Match the Bash 3.2 workflow to script directories. A directory glob covers new wrappers without listing each filename. Co-authored-by: Cursor --- .github/workflows/bash32-script-compat.yml | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/.github/workflows/bash32-script-compat.yml b/.github/workflows/bash32-script-compat.yml index c6eab8ad2..6ba5228d4 100644 --- a/.github/workflows/bash32-script-compat.yml +++ b/.github/workflows/bash32-script-compat.yml @@ -4,23 +4,8 @@ on: push: branches: [ main ] paths: &script-paths - - bin/check-bash32 - - bin/docker-run - - bin/docker-user-args - - bin/normalize-sdk-modes - - bin/php - - bin/php-in-docker - - bin/sandbox-dotnet - - bin/sandbox-java - - bin/sandbox-node - - bin/sandbox-ruby - - sdks/*/bin/dotnet - - sdks/*/bin/gradle - - sdks/*/bin/node - - sdks/*/bin/php - - sdks/*/bin/php-8 - - sdks/*/bin/python - - sdks/*/bin/ruby + - bin/** + - sdks/*/bin/** - sdks/*/run-build - .github/workflows/bash32-script-compat.yml pull_request: From 5ef8e2cbeb11d75b3096ec89a18265c9add4ab1c Mon Sep 17 00:00:00 2001 From: John Spaetzel Date: Wed, 4 Nov 2026 12:49:01 -0800 Subject: [PATCH 5/6] Discover the Bash scripts to check instead of listing them. The workflow already watches these directories, and a new wrapper is picked up when it execs docker-run or php-in-docker. Co-authored-by: Cursor --- bin/check-bash32 | 90 +++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 51 deletions(-) diff --git a/bin/check-bash32 b/bin/check-bash32 index e469f9b1a..858bf33d4 100755 --- a/bin/check-bash32 +++ b/bin/check-bash32 @@ -26,38 +26,41 @@ fi # just a command name; the shell only fails when that line runs. bash4_pattern='(^|[[:space:]])(mapfile|readarray|coproc)([[:space:]]|$)|declare[[:space:]]+-A|\[\[[[:space:]]+-v[[:space:]]|\$\{[A-Za-z_][A-Za-z0-9_]*(,,|\^\^)|\$\{[A-Za-z_][A-Za-z0-9_]*@[A-Za-z]|\$\{[^}]*\[-[0-9]+\]|&>>|[|][&]' -scripts=( - bin/docker-run - bin/docker-user-args - bin/normalize-sdk-modes - bin/php - bin/php-in-docker - bin/sandbox-dotnet - bin/sandbox-java - bin/sandbox-node - bin/sandbox-ruby - sdks/dotnet/bin/dotnet - sdks/dotnet/bin/php - sdks/dotnet/run-build - sdks/java-v1/bin/gradle - sdks/java-v1/bin/php - sdks/java-v1/run-build - sdks/java-v2/bin/gradle - sdks/java-v2/bin/php - sdks/java-v2/run-build - sdks/node/bin/node - sdks/node/bin/php - sdks/node/run-build - sdks/php/bin/php - sdks/php/bin/php-8 - sdks/php/run-build - sdks/python/bin/php - sdks/python/bin/python - sdks/python/run-build - sdks/ruby/bin/php - sdks/ruby/bin/ruby - sdks/ruby/run-build -) +is_bash_script() { + local line + [[ -f "$1" ]] || return 1 + IFS= read -r line < "$1" || return 1 + [[ "${line}" == *bash* ]] +} + +# Wrappers hand their arguments to docker-run or php-in-docker. Those two +# need their own calls, and this file mentions the same command names. +is_docker_wrapper() { + local base + base=$(basename "$1") + case "${base}" in + docker-run|php-in-docker|check-bash32) return 1 ;; + esac + grep -qE 'exec[[:space:]].*(docker-run|php-in-docker)' "$1" +} + +shopt -s nullglob +scripts=() +runtime=() +for path in bin/* sdks/*/bin/* sdks/*/run-build; do + if is_bash_script "${path}"; then + scripts+=("${path}") + if is_docker_wrapper "${path}"; then + runtime+=("${path}") + fi + fi +done +shopt -u nullglob + +if [[ ${#scripts[@]} -eq 0 || ${#runtime[@]} -eq 0 ]]; then + printf 'found no Bash scripts to check\n' >&2 + exit 1 +fi reject_bash4() { local script="$1" @@ -118,7 +121,10 @@ for script in "${scripts[@]}"; do printf 'Bash 3.2 rejected %s\n' "${script}" >&2 exit 1 fi - reject_bash4 "${script}" + # The denylist pattern itself contains the tokens it searches for. + if [[ "${script}" != "bin/check-bash32" ]]; then + reject_bash4 "${script}" + fi done work=$(mktemp -d) @@ -185,24 +191,6 @@ assert_log_contains ok DOCKER_RUN_AS_ROOT=1 run_docker_wrapper "${work}/bin/docker-run" echo ok assert_log_contains root:root -runtime=( - bin/php - sdks/dotnet/bin/dotnet - sdks/dotnet/bin/php - sdks/java-v1/bin/gradle - sdks/java-v1/bin/php - sdks/java-v2/bin/gradle - sdks/java-v2/bin/php - sdks/node/bin/node - sdks/node/bin/php - sdks/php/bin/php - sdks/php/bin/php-8 - sdks/python/bin/php - sdks/python/bin/python - sdks/ruby/bin/php - sdks/ruby/bin/ruby -) - for script in "${runtime[@]}"; do run_docker_wrapper "${work}/${script}" true assert_log_contains --user From 23f89d406479f62bb5247a714f8b3870384fb8b6 Mon Sep 17 00:00:00 2001 From: John Spaetzel Date: Wed, 4 Nov 2026 13:03:29 -0800 Subject: [PATCH 6/6] Use the shared Bash 3.2 action. The macOS Bash check now lives in hellosign/bash32-action, so this repo only passes the script globs. Co-authored-by: Cursor --- .github/workflows/bash32-script-compat.yml | 7 +- bin/check-bash32 | 246 --------------------- 2 files changed, 6 insertions(+), 247 deletions(-) delete mode 100755 bin/check-bash32 diff --git a/.github/workflows/bash32-script-compat.yml b/.github/workflows/bash32-script-compat.yml index 6ba5228d4..bc93a0141 100644 --- a/.github/workflows/bash32-script-compat.yml +++ b/.github/workflows/bash32-script-compat.yml @@ -24,4 +24,9 @@ jobs: uses: actions/checkout@v3 - name: Check SDK scripts on Bash 3.2 - run: ./bin/check-bash32 + uses: hellosign/bash32-action@v1 + with: + paths: | + bin/** + sdks/*/bin/** + sdks/*/run-build diff --git a/bin/check-bash32 b/bin/check-bash32 deleted file mode 100755 index 858bf33d4..000000000 --- a/bin/check-bash32 +++ /dev/null @@ -1,246 +0,0 @@ -#!/usr/bin/env bash - -# macOS ships Bash 3.2. SDK Docker wrappers must keep running there. -# This re-executes under the bash:3.2 image, parses the wrappers, rejects -# Bash 4 syntax that `bash -n` cannot see, and runs them with docker stubbed. - -set -euo pipefail - -ROOT=$(cd "$(dirname "$0")/.." && pwd) - -if [[ "${CHECK_BASH32_INNER:-}" != "1" ]]; then - exec docker run --rm \ - -v "${ROOT}:/work" \ - -w /work \ - -e CHECK_BASH32_INNER=1 \ - "${BASH32_IMAGE:-bash:3.2}" \ - bash bin/check-bash32 -fi - -if [[ "${BASH_VERSINFO[0]}" -ne 3 || "${BASH_VERSINFO[1]}" -ne 2 ]]; then - printf 'expected Bash 3.2, found %s\n' "${BASH_VERSION}" >&2 - exit 1 -fi - -# Bash 4+ tokens. `bash -n` accepts `mapfile` on Bash 3.2 because it is -# just a command name; the shell only fails when that line runs. -bash4_pattern='(^|[[:space:]])(mapfile|readarray|coproc)([[:space:]]|$)|declare[[:space:]]+-A|\[\[[[:space:]]+-v[[:space:]]|\$\{[A-Za-z_][A-Za-z0-9_]*(,,|\^\^)|\$\{[A-Za-z_][A-Za-z0-9_]*@[A-Za-z]|\$\{[^}]*\[-[0-9]+\]|&>>|[|][&]' - -is_bash_script() { - local line - [[ -f "$1" ]] || return 1 - IFS= read -r line < "$1" || return 1 - [[ "${line}" == *bash* ]] -} - -# Wrappers hand their arguments to docker-run or php-in-docker. Those two -# need their own calls, and this file mentions the same command names. -is_docker_wrapper() { - local base - base=$(basename "$1") - case "${base}" in - docker-run|php-in-docker|check-bash32) return 1 ;; - esac - grep -qE 'exec[[:space:]].*(docker-run|php-in-docker)' "$1" -} - -shopt -s nullglob -scripts=() -runtime=() -for path in bin/* sdks/*/bin/* sdks/*/run-build; do - if is_bash_script "${path}"; then - scripts+=("${path}") - if is_docker_wrapper "${path}"; then - runtime+=("${path}") - fi - fi -done -shopt -u nullglob - -if [[ ${#scripts[@]} -eq 0 || ${#runtime[@]} -eq 0 ]]; then - printf 'found no Bash scripts to check\n' >&2 - exit 1 -fi - -reject_bash4() { - local script="$1" - local line - line=$(grep -nE "${bash4_pattern}" "${script}" | grep -vE '^[0-9]+:[[:space:]]*#' || true) - if [[ -n "${line}" ]]; then - printf 'Bash 4+ syntax in %s:\n%s\n' "${script}" "${line}" >&2 - return 1 - fi -} - -self_test_denylist() { - local tmp - tmp=$(mktemp) - printf '#!/usr/bin/env bash\n# mapfile stays in this comment\necho ok\n' > "${tmp}" - reject_bash4 "${tmp}" - printf '#!/usr/bin/env bash\nmapfile -t xs < /dev/null\n' > "${tmp}" - if reject_bash4 "${tmp}" 2>/dev/null; then - rm -f "${tmp}" - printf 'denylist did not reject mapfile\n' >&2 - exit 1 - fi - rm -f "${tmp}" -} - -assert_log_contains() { - local needle="$1" - if ! grep -Fxq -e "${needle}" "${DOCKER_RUN_LOG}"; then - printf 'expected docker argument <%s>\nactual:\n' "${needle}" >&2 - cat "${DOCKER_RUN_LOG}" >&2 - exit 1 - fi -} - -assert_log_excludes() { - local needle="$1" - if grep -Fxq -e "${needle}" "${DOCKER_RUN_LOG}"; then - printf 'unexpected docker argument <%s>\nactual:\n' "${needle}" >&2 - cat "${DOCKER_RUN_LOG}" >&2 - exit 1 - fi -} - -run_docker_wrapper() { - : > "${DOCKER_RUN_LOG}" - if ! "$@"; then - printf 'command failed:' >&2 - printf ' %s' "$@" >&2 - printf '\n' >&2 - exit 1 - fi -} - -self_test_denylist - -for script in "${scripts[@]}"; do - if ! bash -n "${script}"; then - printf 'Bash 3.2 rejected %s\n' "${script}" >&2 - exit 1 - fi - # The denylist pattern itself contains the tokens it searches for. - if [[ "${script}" != "bin/check-bash32" ]]; then - reject_bash4 "${script}" - fi -done - -work=$(mktemp -d) -stubdir=$(mktemp -d) -trap 'rm -rf "${work}" "${stubdir}"' EXIT - -for script in "${scripts[@]}"; do - mkdir -p "${work}/$(dirname "${script}")" - cp "${script}" "${work}/${script}" -done - -cat > "${stubdir}/docker" <<'EOF' -#!/usr/bin/env bash -set -euo pipefail -if [[ "${1:-}" == "info" ]]; then - if [[ -n "${DOCKER_INFO_SECURITY:-}" ]]; then - printf '%s\n' "${DOCKER_INFO_SECURITY}" - fi - exit 0 -fi -if [[ "${1:-}" == "run" ]]; then - printf '%s\n' "$@" >> "${DOCKER_RUN_LOG:?}" - exit 0 -fi -printf 'unexpected docker command: %s\n' "$*" >&2 -exit 1 -EOF -chmod +x "${stubdir}/docker" - -export PATH="${stubdir}:${PATH}" -DOCKER_RUN_LOG=$(mktemp) -export DOCKER_RUN_LOG -unset DOCKER_RUN_AS_ROOT DOCKER_INFO_SECURITY PHP_DOCKER_PLATFORM PHP_USE_NATIVE GITHUB_ACTIONS - -host_user="$(id -u):$(id -g)" - -user_args=$("${work}/bin/docker-user-args") -expected=$(printf '%s\n' --user "${host_user}") -if [[ "${user_args}" != "${expected}" ]]; then - printf 'rootful docker-user-args returned:\n%s\n' "${user_args}" >&2 - exit 1 -fi - -user_args=$(DOCKER_RUN_AS_ROOT=1 "${work}/bin/docker-user-args") -expected=$(printf '%s\n' --user root:root) -if [[ "${user_args}" != "${expected}" ]]; then - printf 'DOCKER_RUN_AS_ROOT docker-user-args returned:\n%s\n' "${user_args}" >&2 - exit 1 -fi - -user_args=$(DOCKER_INFO_SECURITY=name=rootless "${work}/bin/docker-user-args") -if [[ "${user_args}" != "${expected}" ]]; then - printf 'rootless docker-user-args returned:\n%s\n' "${user_args}" >&2 - exit 1 -fi - -run_docker_wrapper "${work}/bin/docker-run" echo ok -assert_log_contains --rm -assert_log_contains --user -assert_log_contains "${host_user}" -assert_log_contains echo -assert_log_contains ok - -DOCKER_RUN_AS_ROOT=1 run_docker_wrapper "${work}/bin/docker-run" echo ok -assert_log_contains root:root - -for script in "${runtime[@]}"; do - run_docker_wrapper "${work}/${script}" true - assert_log_contains --user - assert_log_contains "${host_user}" - assert_log_contains true -done - -run_docker_wrapper "${work}/bin/php" composer install -assert_log_excludes --platform -assert_log_contains jtreminio/php-cli:7.4 - -run_docker_wrapper "${work}/sdks/node/bin/php" composer install -assert_log_excludes --platform - -run_docker_wrapper "${work}/sdks/php/bin/php" php -v -assert_log_contains --platform -assert_log_contains linux/amd64 -assert_log_contains jtreminio/php-cli:8.1 - -run_docker_wrapper "${work}/sdks/php/bin/php-8" php -v -assert_log_contains --platform -assert_log_contains jtreminio/php:8.1 - -run_docker_wrapper "${work}/sdks/dotnet/bin/dotnet" dotnet test -assert_log_contains mcr.microsoft.com/dotnet/sdk:10.0 - -: > "${DOCKER_RUN_LOG}" -PHP_USE_NATIVE=1 "${work}/sdks/php/bin/php" true -if [[ -s "${DOCKER_RUN_LOG}" ]]; then - printf 'PHP_USE_NATIVE=1 invoked docker:\n' >&2 - cat "${DOCKER_RUN_LOG}" >&2 - exit 1 -fi - -sample=$(mktemp) -printf 'x\n' > "${sample}" -chmod 755 "${sample}" -"${work}/bin/normalize-sdk-modes" "${sample}" -mode=$(stat -c %a "${sample}") -if [[ "${mode}" != "644" ]]; then - printf 'normalize-sdk-modes left mode %s\n' "${mode}" >&2 - exit 1 -fi -chmod 755 "${sample}" -GITHUB_ACTIONS=1 "${work}/bin/normalize-sdk-modes" "${sample}" -mode=$(stat -c %a "${sample}") -if [[ "${mode}" != "755" ]]; then - printf 'normalize-sdk-modes changed mode to %s on GitHub Actions\n' "${mode}" >&2 - exit 1 -fi -rm -f "${sample}" - -printf 'Bash %s accepted the SDK Docker scripts\n' "${BASH_VERSION}"