diff --git a/.github/workflows/node-simple-pnpm.yaml b/.github/workflows/node-simple-pnpm.yaml index 764f79a1..b35d737b 100644 --- a/.github/workflows/node-simple-pnpm.yaml +++ b/.github/workflows/node-simple-pnpm.yaml @@ -471,6 +471,71 @@ jobs: npm-pkg-version: ${{ fromJSON(steps.npm-pkg-metadata.outputs.data).npm-pkg-version }} pnpm-version: ${{ fromJSON(steps.npm-pkg-metadata.outputs.data).pnpm-version }} + preflight-require-latest: + name: preflight (require-latest) + runs-on: ubuntu-latest + # Surface SDK-version drift on PRs as a non-blocking check, but enforce it + # on the default branch and in the merge queue so publish cannot proceed + # with a stale @platforma-sdk dependency. + continue-on-error: ${{ github.ref_name != inputs.changeset-default-branch && github.event_name != 'merge_group' }} + needs: + - init + steps: + - uses: milaboratory/github-ci/actions/context@v4 + + - uses: milaboratory/github-ci/actions/env@v4 + with: + inputs: ${{ inputs.env }} + secrets: ${{ secrets.env }} + + - uses: actions/checkout@v4 + with: + lfs: ${{ inputs.checkout-git-lfs }} + submodules: ${{ inputs.checkout-submodules }} + fetch-depth: '0' + + - name: Check infrastructure requirements for publication + uses: milaboratory/github-ci/actions/node/require-latest@v4 + with: + packages: | + @platforma-sdk/block-tools + @platforma-sdk/tengo-builder + + preflight-pnpm-lock-sync: + name: preflight (pnpm-lock-sync) + runs-on: ubuntu-latest + # Surface pnpm-workspace.yaml / pnpm-lock.yaml drift on PRs as a + # non-blocking check, but enforce it on the default branch and in the + # merge queue so publish cannot proceed with a stale lockfile. + continue-on-error: ${{ github.ref_name != inputs.changeset-default-branch && github.event_name != 'merge_group' }} + needs: + - init + steps: + - uses: milaboratory/github-ci/actions/context@v4 + + - uses: milaboratory/github-ci/actions/env@v4 + with: + inputs: ${{ inputs.env }} + secrets: ${{ secrets.env }} + + - uses: actions/checkout@v4 + with: + lfs: ${{ inputs.checkout-git-lfs }} + submodules: ${{ inputs.checkout-submodules }} + fetch-depth: '0' + + - name: Check pnpm-lock.yaml is in sync with pnpm-workspace.yaml + shell: bash + env: + DEFAULT_BRANCH: origin/${{ inputs.changeset-default-branch }} + run: | + if git diff --name-only ${DEFAULT_BRANCH}..HEAD | grep -q -E '^pnpm-workspace.yaml$'; then + if ! git diff --name-only ${DEFAULT_BRANCH}..HEAD | grep -q -E '^pnpm-lock.yaml$'; then + echo "Changes in pnpm-workspace.yaml detected, but no updates in pnpm-lock.yaml were found in current branch" + exit 1 + fi + fi + check-changesets: name: check for changesets runs-on: ubuntu-latest @@ -525,11 +590,21 @@ jobs: matrix: include: ${{ fromJSON(inputs.pre-calculated-task-list) }} needs: + - preflight-require-latest + - preflight-pnpm-lock-sync - check-changesets - metadata if: > inputs.pre-calculated && inputs.pre-calculated-task-list != '[]' && !failure() && !cancelled() && + ( + needs.preflight-require-latest.result == 'success' || + needs.preflight-require-latest.result == 'skipped' + ) && + ( + needs.preflight-pnpm-lock-sync.result == 'success' || + needs.preflight-pnpm-lock-sync.result == 'skipped' + ) && ( needs.check-changesets.result == 'success' || needs.check-changesets.result == 'skipped' @@ -608,17 +683,7 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPMJS_TOKEN: ${{ env.NPMJS_TOKEN }} - DEFAULT_BRANCH: origin/${{ inputs.changeset-default-branch }} - run: | - if git diff --name-only ${DEFAULT_BRANCH}..HEAD | grep -q -E '^pnpm-workspace.yaml$'; then - # Changes in pnpm-workspace.yaml have to be accompanied by pnpm-lock.yaml update - if ! git diff --name-only ${DEFAULT_BRANCH}..HEAD | grep -q -E '^pnpm-lock.yaml$'; then - echo "Changes in pnpm-workspace.yaml detected, but no updates in pnpm-lock.yaml were found in current branch" - exit 1 - fi - fi - pnpm install --frozen-lockfile --prefer-offline - name: Run changeset version @@ -641,11 +706,21 @@ jobs: name: unified (build test publish) runs-on: ${{ inputs.gha-runner-label }} needs: + - preflight-require-latest + - preflight-pnpm-lock-sync - check-changesets - metadata - pre-calculated-build if: > !failure() && !cancelled() && + ( + needs.preflight-require-latest.result == 'success' || + needs.preflight-require-latest.result == 'skipped' + ) && + ( + needs.preflight-pnpm-lock-sync.result == 'success' || + needs.preflight-pnpm-lock-sync.result == 'skipped' + ) && ( needs.pre-calculated-build.result == 'success' || needs.pre-calculated-build.result == 'skipped' @@ -684,13 +759,6 @@ jobs: token: ${{ steps.app-token.outputs.token }} fetch-depth: '0' - - name: Check infrastructure requirements for publication - uses: milaboratory/github-ci/actions/node/require-latest@v4 - with: - packages: | - @platforma-sdk/block-tools - @platforma-sdk/tengo-builder - - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: @@ -751,17 +819,7 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPMJS_TOKEN: ${{ env.NPMJS_TOKEN }} - DEFAULT_BRANCH: origin/${{ inputs.changeset-default-branch }} - run: | - if git diff --name-only ${DEFAULT_BRANCH}..HEAD | grep -q -E '^pnpm-workspace.yaml$'; then - # Changes in pnpm-workspace.yaml have to be accompanied by pnpm-lock.yaml update - if ! git diff --name-only ${DEFAULT_BRANCH}..HEAD | grep -q -E '^pnpm-lock.yaml$'; then - echo "Changes in pnpm-workspace.yaml detected, but no updates in pnpm-lock.yaml were found in current branch" - exit 1 - fi - fi - pnpm install --frozen-lockfile --prefer-offline - name: Run changeset version @@ -945,6 +1003,8 @@ jobs: needs: - init - metadata + - preflight-require-latest + - preflight-pnpm-lock-sync - check-changesets - build-test-publish - pre-calculated-build @@ -966,6 +1026,8 @@ jobs: ${{ needs.pre-calculated-build.result }} ${{ needs.build-test-publish.result }} ${{ needs.check-changesets.result }} + ${{ needs.preflight-require-latest.result }} + ${{ needs.preflight-pnpm-lock-sync.result }} product-name: ${{ inputs.app-name }} override-version: ${{ format('{0}', env.NPM_PKG_VERSION) }} override-tag: ${{ format('v{0}', env.NPM_PKG_VERSION) }} diff --git a/actions/docker/pl-compose/action.yaml b/actions/docker/pl-compose/action.yaml index fbe4a8ce..0364f4a0 100644 --- a/actions/docker/pl-compose/action.yaml +++ b/actions/docker/pl-compose/action.yaml @@ -105,6 +105,23 @@ runs: # depending on moon phases and mood of the scheduler god. :( export PL_MAIN_ROOT="$(TMPDIR=${RUNNER_WORKSPACE} mktemp --directory)" + # Run platforma as the runner user (not root) so OS file-mode permissions + # are enforced inside the container (required by exec.writable tests). + # PL_UID/PL_GID are consumed by docker-compose.yaml. + export PL_UID="$(id -u)" + export PL_GID="$(id -g)" + echo "Platforma container will run as ${PL_UID}:${PL_GID}" + + # pl entrypoint (run-pl.sh) calls `id --user --name` under `set -o errexit`, + # which fails when the runner UID has no /etc/passwd entry. + # Synthesize minimal passwd + group files for the chosen UID/GID and + # bind-mount them into the container (paths exported for compose). + mkdir -p "${PL_MAIN_ROOT}/.users" + printf 'root:x:0:0:root:/root:/bin/sh\npl:x:%s:%s:pl:/pl-home:/bin/sh\n' "${PL_UID}" "${PL_GID}" > "${PL_MAIN_ROOT}/.users/passwd" + printf 'root:x:0:\npl:x:%s:\n' "${PL_GID}" > "${PL_MAIN_ROOT}/.users/group" + export PL_PASSWD_PATH="${PL_MAIN_ROOT}/.users/passwd" + export PL_GROUP_PATH="${PL_MAIN_ROOT}/.users/group" + echo "main-root=${PL_MAIN_ROOT}" >> "${GITHUB_OUTPUT}" docker compose --file "${ACTION_PATH}/docker-compose.yaml" config | tee "${PL_MAIN_ROOT}/compose.yaml" diff --git a/actions/docker/pl-compose/docker-compose.yaml b/actions/docker/pl-compose/docker-compose.yaml index 80a20aa3..febfb10b 100644 --- a/actions/docker/pl-compose/docker-compose.yaml +++ b/actions/docker/pl-compose/docker-compose.yaml @@ -3,6 +3,9 @@ name: "platform" services: minio: image: quay.io/minio/minio + # Run as the runner UID/GID — keeps all files in PL_MAIN_ROOT owned by + # one user so cleanup (rm -rf) works without sudo. + user: "${PL_UID}:${PL_GID}" command: server /data/minio --address "0.0.0.0:9000" --console-address "0.0.0.0:9001" ports: @@ -18,7 +21,7 @@ services: platforma: image: ${PL_DOCKER_REGISTRY}:${PL_DOCKER_TAG} - user: root + user: "${PL_UID}:${PL_GID}" # Packages can be large. We don't want to save them after execution. command: | @@ -28,7 +31,7 @@ services: --log-dst="file:///storage/log/platforma.log" --log-level="${PL_LOG_LEVEL:-info}" --main-root="/storage" - --packages-dir="/packages" + --packages-dir="/storage/packages" --primary-storage-s3="http://minio:9000/platforma-primary-bucket" --primary-storage-s3-key="testuser" --primary-storage-s3-secret="testpassword" @@ -38,9 +41,11 @@ services: ports: - "6345:6345" - tmpfs: [ /tmp ] + # tmpfs HOME so pl/child processes have a writable home with no host pollution. + tmpfs: [ /tmp, /pl-home ] environment: + - "HOME=/pl-home" - "MI_LICENSE=${MI_LICENSE:-${PL_LICENSE:-}}" - "PL_LICENSE=${PL_LICENSE:-}" - "PL_DATA_CREATE_BUCKET=${PL_DATA_CREATE_BUCKET:-true}" @@ -50,6 +55,10 @@ services: - ${PL_TEST_ASSETS_DIR}:/library - ${PL_WORKSPACE}:${PL_WORKSPACE} - ${HTPASSWD_PATH}:/etc/htpasswd + # Inject passwd/group so the runner UID resolves to a name — + # required by pl entrypoint's `id --user --name` check. + - ${PL_PASSWD_PATH}:/etc/passwd:ro + - ${PL_GROUP_PATH}:/etc/group:ro restart: always