This is an automated email from the ASF dual-hosted git repository.

ajfabbri pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/hadoop.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 6337d964188 HADOOP-19893: S3A CI tests for fork PRs (#8530)
6337d964188 is described below

commit 6337d9641880610c4d1bddba17da2e9f0484a00d
Author: Aaron Fabbri <[email protected]>
AuthorDate: Fri Jul 24 15:25:39 2026 -0700

    HADOOP-19893: S3A CI tests for fork PRs (#8530)
    
    * HADOOP-19893. Add manual cloud_aws CI trigger for fork PRs
    
    - Add workflow_dispatch inputs in cloud_aws.yml to run CI by fork PR number
    - Resolve PR head repo/SHA via API and run tests against that exact fork 
commit.
    - Keep automatic cloud_aws CI runs only for same-repo pull_request events.
    - tmpl_cloud_aws.yml adds checkout_repository/checkout_ref inputs.
    - Pass LOCALSTACK_CI_KEY explicitly to reusable workflow (required).
    - Post S3A Test Summary commit status for manual runs when CI wasn't 
skipped.
    - Stash PR number and branch repo details for reporting workflow.
    - Tweak report_cloud_aws.yml to consume PR details, using stashed target 
commit
      for report.
    - Add sticky comment hint to S3A PRs from forks
    - Clarify status of manual trigger notification
    
    - ci: update docker/login-action uses
    
    The existing version SHA did not match the ASF Infra's action.yml whitelist,
    causing CI to fail to start.
---
 .github/actions/build_image/action.yml             |   2 +-
 .../gha-tests/hadoop-aws-localstack-excludes.txt   |   7 ++
 .github/workflows/cloud_aws.yml                    | 101 ++++++++++++++++-
 .github/workflows/notify_cloud_aws.yml             | 120 +++++++++++++++++++++
 .github/workflows/report_cloud_aws.yml             |  37 ++++++-
 .github/workflows/tmpl_build_and_test.yml          |   2 +-
 .github/workflows/tmpl_build_image_cache.yml       |   2 +-
 .github/workflows/tmpl_cloud_aws.yml               |  26 ++++-
 8 files changed, 287 insertions(+), 10 deletions(-)

diff --git a/.github/actions/build_image/action.yml 
b/.github/actions/build_image/action.yml
index 9027c1d02d3..810813e9aec 100644
--- a/.github/actions/build_image/action.yml
+++ b/.github/actions/build_image/action.yml
@@ -37,7 +37,7 @@ runs:
   using: composite
   steps:
     - name: Login to GitHub Container Registry
-      uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 
v4.4.0
+      uses: docker/login-action@06fb636fac595d6fb4b28a5dfcb21a6f5091859c # 
v4.5.0
       with:
         registry: ghcr.io
         username: ${{ github.actor }}
diff --git a/.github/gha-tests/hadoop-aws-localstack-excludes.txt 
b/.github/gha-tests/hadoop-aws-localstack-excludes.txt
index d16ff8957a4..3e4fa234e11 100644
--- a/.github/gha-tests/hadoop-aws-localstack-excludes.txt
+++ b/.github/gha-tests/hadoop-aws-localstack-excludes.txt
@@ -23,6 +23,8 @@
 # TODO see if we can enable any of these...
 
 # tests that depend on public S3 buckets
+# TODO may be able disable these with s3a test config (see testing.md under
+# third party)
 **/org/apache/hadoop/fs/s3a/scale/ITestS3AInputStreamPerformance.java
 **/org/apache/hadoop/fs/s3a/ITestS3ARequesterPays.java
 **/org/apache/hadoop/fs/s3a/s3guard/ITestS3GuardTool.java
@@ -48,6 +50,7 @@
 #   expected: <true> but was: <false>
 # 2. testUpdateDeepDirectoryStructureToRemote():
 # AssertionFailedError: Files Copied value 2 above maximum 1 ==> expected: 
<true> but was: <false>
+# TODO see if this is a timestamp issue
 **/org/apache/hadoop/fs/contract/s3a/ITestS3AContractDistCp.java
 
 # A number of failures with vectored read tests
@@ -59,6 +62,8 @@
 # s3a://hadoop-ci/job-00/test:
 # software.amazon.awssdk.services.s3.model.S3Exception: The AWS Access Key Id 
you
 # provided does not exist in our records. (Service: S3, Status Code: 403
+# TODO see if tests are still connecting to AWS (validate which endpoint is
+# being used)
 **/org/apache/hadoop/fs/s3a/ITestS3APrefetchingCacheFiles.java
 **/org/apache/hadoop/fs/s3a/ITestS3AFailureHandling.java
 
@@ -76,4 +81,6 @@
 
 # testSeeksWithLruEviction java.util.concurrent.TimeoutException: timed out
 # after 180 seconds
+# TODO consider removing this feature & test. This impl. is problematic,
+# vectored IO addresses some of this, and the Amazon analytics input stream 
addresses the rest of it.
 **/org/apache/hadoop/fs/s3a/ITestS3APrefetchingLruEviction.java
diff --git a/.github/workflows/cloud_aws.yml b/.github/workflows/cloud_aws.yml
index 7d48413fb0d..c33690887dc 100644
--- a/.github/workflows/cloud_aws.yml
+++ b/.github/workflows/cloud_aws.yml
@@ -20,6 +20,7 @@
 name: "Cloud-AWS"
 
 on:
+  # pull requests will automatically trigger S3A tests for non-forked PRs
   pull_request:
     paths:
       - 'hadoop-tools/hadoop-aws/**'
@@ -27,8 +28,88 @@ on:
       - '.github/actions/build_image**'
       - '.github/gha-tests/hadoop-aws*excludes.txt'
 
+  # For fork PRs, S3A integration tests must be manually triggered.
+  # Although our auth. key for localstack is not a very sensitive secret,
+  # we don't want to leak it to PRs that we haven't reviewed yet.
+  workflow_dispatch:
+    inputs:
+      pr_number:
+        description: Pull request number from a fork repository
+        required: true
+        type: string
+      java:
+        description: Java version for the Cloud-AWS run
+        required: false
+        type: string
+        default: '25'
+      os:
+        description: OS image key for the test container
+        required: false
+        type: string
+        default: ubuntu_24
+      runner_os:
+        description: Runner label used to execute jobs
+        required: false
+        type: string
+        default: ubuntu-24.04
+
 jobs:
+  # Stash PR's repository and commit, so we can properly add a test summary
+  # in both the forked-repo, and non-forked case.
+  resolve-fork-pr:
+    if: github.event_name == 'workflow_dispatch'
+    runs-on: ubuntu-slim
+    permissions:
+      pull-requests: read
+    outputs:
+      head_repo: ${{ steps.pr.outputs.head_repo }}
+      head_sha: ${{ steps.pr.outputs.head_sha }}
+    steps:
+      - name: Resolve PR head SHA and repository
+        id: pr
+        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # 
v9
+        with:
+          script: |
+            const prNumber = Number('${{ inputs.pr_number }}');
+            if (!Number.isInteger(prNumber) || prNumber <= 0) {
+              throw new Error(`Invalid pull request number: '${{ 
inputs.pr_number }}'`);
+            }
+
+            const { data: pr } = await github.rest.pulls.get({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              pull_number: prNumber,
+            });
+
+            if (!pr.head.repo.fork) {
+              throw new Error(`PR #${prNumber} is not from a fork repository. 
Use the pull_request-triggered Cloud-AWS workflow instead.`);
+            }
+
+            core.setOutput('head_repo', pr.head.repo.full_name);
+            core.setOutput('head_sha', pr.head.sha);
+
+            console.log(`Resolved PR #${prNumber}: 
${pr.head.repo.full_name}@${pr.head.sha}`);
+
+      - name: Write manual target metadata
+        shell: bash
+        run: |
+          cat > manual-target.json <<EOF
+          {
+            "pr_number": "${{ inputs.pr_number }}",
+            "head_repo": "${{ steps.pr.outputs.head_repo }}",
+            "head_sha": "${{ steps.pr.outputs.head_sha }}"
+          }
+          EOF
+
+      - name: Upload manual target metadata
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a 
# v7.0.1
+        with:
+          name: cloud-aws-manual-target
+          path: manual-target.json
+          retention-days: 7
+
   run-aws-integration:
+    if: github.event_name == 'pull_request' && 
github.event.pull_request.head.repo.full_name == github.repository
     # Security: write privileges are needed to update PR status and upload 
test results.
     # Package write is for building toolchain container images on demand, but 
ghcr.io access is
     # scoped to the repository the actions run on.
@@ -37,8 +118,26 @@ jobs:
       contents: read
     name: Run
     uses: ./.github/workflows/tmpl_cloud_aws.yml
-    secrets: inherit    # inherit LOCALSTACK_CI_KEY
+    secrets:
+      LOCALSTACK_CI_KEY: ${{ secrets.LOCALSTACK_CI_KEY }}
     with:
       java: 25
       os: ubuntu_24
       runner_os: ubuntu-24.04
+
+  run-aws-integration-manual:
+    if: github.event_name == 'workflow_dispatch'
+    needs: [ resolve-fork-pr ]
+    permissions:
+      packages: write
+      contents: read
+    name: Run (manual fork PR)
+    uses: ./.github/workflows/tmpl_cloud_aws.yml
+    secrets:
+      LOCALSTACK_CI_KEY: ${{ secrets.LOCALSTACK_CI_KEY }}
+    with:
+      java: ${{ inputs.java }}
+      os: ${{ inputs.os }}
+      runner_os: ${{ inputs.runner_os }}
+      checkout_repository: ${{ needs.resolve-fork-pr.outputs.head_repo }}
+      checkout_ref: ${{ needs.resolve-fork-pr.outputs.head_sha }}
diff --git a/.github/workflows/notify_cloud_aws.yml 
b/.github/workflows/notify_cloud_aws.yml
new file mode 100644
index 00000000000..d9e9140a98b
--- /dev/null
+++ b/.github/workflows/notify_cloud_aws.yml
@@ -0,0 +1,120 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Add a sticky comment to hadoop-aws PRs from forked repos with a hint that
+# integration tests must be manually triggered by a maintainer.
+#
+name: "Cloud-AWS PR Update"
+
+# Security: This privileged workflow uses pull_request_target but does not
+# check out or execute untrusted code. It only creates a check run and a PR
+# comment in the base repository.
+on:
+  pull_request_target:
+    types: [opened, reopened, synchronize]
+    paths:
+      - 'hadoop-tools/hadoop-aws/**'
+      - '.github/workflows/*cloud_aws.yml'
+      - '.github/actions/build_image**'
+      - '.github/gha-tests/hadoop-aws*excludes.txt'
+
+jobs:
+  notify:
+    if: github.event.pull_request.head.repo.full_name != github.repository
+    name: "Notify Cloud-AWS"
+    runs-on: ubuntu-slim
+    permissions:
+      checks: write
+      pull-requests: write
+    steps:
+      - name: Post approval-required check and sticky comment
+        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # 
v9
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          script: |
+            const marker = '<!-- cloud-aws-fork-pr-note -->';
+            const pr = context.payload.pull_request;
+            const workflowUrl = 
`https://github.com/${context.repo.owner}/${context.repo.repo}/actions/workflows/cloud_aws.yml`;
+            const dispatchCommand = `gh workflow run cloud_aws.yml -R 
${context.repo.owner}/${context.repo.repo} -f pr_number=${pr.number}`;
+
+            const checkTitle = 'Cloud-AWS manual trigger instructions';
+            const checkSummary = [
+              'S3A tests must be manually triggered for fork pull requests.',
+              'A maintainer should:\n',
+              '- Confirm the PR does not contain risky changes to actions in',
+              '  .github/workflows and .github/actions.\n',
+              '- *Approve workflows* for this fork PR if prompted.\n',
+              `- Run [Cloud-AWS workflow](${workflowUrl}) with 
\`pr_number=${pr.number}\``,
+              `  (or via CLI: \`${dispatchCommand}\`)\n`,
+              '\n',
+              `fork head: \`${pr.head.repo.full_name}@${pr.head.sha.slice(0, 
12)}\``,
+            ].join(' ');
+
+            await github.rest.checks.create({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              name: 'Cloud-AWS / Manual Trigger Info',
+              head_sha: pr.head.sha,
+              status: 'completed',
+              conclusion: 'neutral',
+              output: {
+                title: checkTitle,
+                summary: checkSummary,
+              },
+            });
+
+            const commentBody = [
+              marker,
+              `ℹ️ **${checkTitle}**: ${checkSummary}`,
+            ].join('\n');
+
+            try {
+              const comments = await 
github.paginate(github.rest.issues.listComments, {
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                issue_number: pr.number,
+                per_page: 100,
+              });
+
+              const existing = comments.find(comment => comment.body && 
comment.body.includes(marker));
+              if (existing) {
+                await github.rest.issues.updateComment({
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  comment_id: existing.id,
+                  body: commentBody,
+                });
+                core.info(`Updated sticky comment ${existing.id}`);
+              } else {
+                const created = await github.rest.issues.createComment({
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  issue_number: pr.number,
+                  body: commentBody,
+                });
+                core.info(`Created sticky comment ${created.data.id}`);
+              }
+            } catch (error) {
+              if (error.status === 403) {
+                core.warning('Unable to write sticky PR comment due to repo 
token policy.');
+                core.warning(`GitHub API message: ${error.message}`);
+              } else {
+                throw error;
+              }
+            }
diff --git a/.github/workflows/report_cloud_aws.yml 
b/.github/workflows/report_cloud_aws.yml
index 5ea9037dda3..75a78711b2e 100644
--- a/.github/workflows/report_cloud_aws.yml
+++ b/.github/workflows/report_cloud_aws.yml
@@ -32,8 +32,39 @@ permissions:
 jobs:
   report:
     runs-on: ubuntu-latest
-    if: github.event.workflow_run.event == 'pull_request'
+    if: (github.event.workflow_run.event == 'pull_request' || 
github.event.workflow_run.event == 'workflow_dispatch') && 
github.event.workflow_run.conclusion != 'skipped'
     steps:
+      - name: Download manual target metadata
+        if: github.event.workflow_run.event == 'workflow_dispatch'
+        uses: 
actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+        with:
+          name: cloud-aws-manual-target
+          run-id: ${{ github.event.workflow_run.id }}
+          path: manual-target
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Resolve status target SHA
+        id: target_sha
+        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # 
v9
+        env:
+          WORKFLOW_EVENT: ${{ github.event.workflow_run.event }}
+          WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
+        with:
+          script: |
+            const fs = require('node:fs');
+
+            let targetSha = process.env.WORKFLOW_HEAD_SHA;
+            if (process.env.WORKFLOW_EVENT === 'workflow_dispatch') {
+              const payload = 
JSON.parse(fs.readFileSync('manual-target/manual-target.json', 'utf8'));
+              if (!payload.head_sha) {
+                throw new Error('manual-target.json is missing head_sha');
+              }
+              targetSha = payload.head_sha;
+            }
+
+            core.setOutput('value', targetSha);
+            console.log(`Status target SHA: ${targetSha}`);
+
       - name: S3A Test Report
         id: report
         uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2  # 
v3.0.0
@@ -53,7 +84,7 @@ jobs:
           fail-on-error: 'false'
 
       - name: Post S3A commit status
-        uses: actions/github-script@v7
+        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # 
v9
         if: always()
         with:
           script: |
@@ -78,7 +109,7 @@ jobs:
 
             const params = {
               ...context.repo,
-              sha:         context.payload.workflow_run.head_sha,
+              sha:         '${{ steps.target_sha.outputs.value }}',
               state,
               description,
               context:     'S3A Test Summary',
diff --git a/.github/workflows/tmpl_build_and_test.yml 
b/.github/workflows/tmpl_build_and_test.yml
index ac04faa78df..e5beb7cfa41 100644
--- a/.github/workflows/tmpl_build_and_test.yml
+++ b/.github/workflows/tmpl_build_and_test.yml
@@ -118,7 +118,7 @@ jobs:
         with:
           fetch-depth: 0
       - name: Login to GitHub Container Registry
-        uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 
v4.4.0
+        uses: docker/login-action@06fb636fac595d6fb4b28a5dfcb21a6f5091859c # 
v4.5.0
         with:
           registry: ghcr.io
           username: ${{ github.actor }}
diff --git a/.github/workflows/tmpl_build_image_cache.yml 
b/.github/workflows/tmpl_build_image_cache.yml
index ad8c792c957..0e609ca17ec 100644
--- a/.github/workflows/tmpl_build_image_cache.yml
+++ b/.github/workflows/tmpl_build_image_cache.yml
@@ -43,7 +43,7 @@ jobs:
       - name: Set up Docker Buildx
         uses: 
docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
       - name: Login to DockerHub
-        uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # 
v4.4.0
+        uses: docker/login-action@06fb636fac595d6fb4b28a5dfcb21a6f5091859c # 
v4.5.0
         with:
           registry: ghcr.io
           username: ${{ github.actor }}
diff --git a/.github/workflows/tmpl_cloud_aws.yml 
b/.github/workflows/tmpl_cloud_aws.yml
index 18edf2bab3a..4937bdb69c7 100644
--- a/.github/workflows/tmpl_cloud_aws.yml
+++ b/.github/workflows/tmpl_cloud_aws.yml
@@ -38,6 +38,17 @@ on:
         type: string
         description: OS tag for runner (e.g., Linux, ubuntu-24.04)
         default: ubuntu_24.04
+      checkout_repository:
+        required: false
+        type: string
+        description: Repository to checkout for this run
+      checkout_ref:
+        required: false
+        type: string
+        description: Ref or SHA to checkout for this run
+    secrets:
+      LOCALSTACK_CI_KEY:
+        required: true
 
 # Security: Minimal defaults for workflow.
 permissions: {}
@@ -61,10 +72,11 @@ jobs:
     outputs:
       build_image_url: ${{ steps.img.outputs.build_image_url }}
     steps:
-      - uses: actions/checkout@v6
+      - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # 
v6.1.0
         with:
-          # Full fetch so build image URL can be computed for any branch
-          fetch-depth: 0
+          repository: ${{ inputs.checkout_repository || github.repository }}
+          ref: ${{ inputs.checkout_ref || github.ref }}
+          persist-credentials: false
       - uses: ./.github/actions/build_image_url
         id: img
         with:
@@ -87,6 +99,10 @@ jobs:
         run: |
           echo "Build image URL: ${{ 
needs.precondition.outputs.build_image_url }}"
       - uses: actions/checkout@v6
+        with:
+          repository: ${{ inputs.checkout_repository || github.repository }}
+          ref: ${{ inputs.checkout_ref || github.ref }}
+          persist-credentials: false
       - uses: ./.github/actions/build_image
         id: build_img
         with:
@@ -141,6 +157,10 @@ jobs:
         -Dcheckstyle.skip -Dspotbugs.skip -Denforcer.skip -Drat.skip
     steps:
       - uses: actions/checkout@v6
+        with:
+          repository: ${{ inputs.checkout_repository || github.repository }}
+          ref: ${{ inputs.checkout_ref || github.ref }}
+          persist-credentials: false
       # Performance: Caching TODO: We need to create a centralized maven build 
cache that is
       # built on trunk. This will always miss on a new PR: Caches can't be
       # shared between PR branches. PR branches *can* access caches from their


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to