This is an automated email from the ASF dual-hosted git repository.
lewismc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nutch.git
The following commit(s) were added to refs/heads/master by this push:
new 21a17b303 Trigger single-node Hadoop smoke test from PR /smoke-test
comments and release tags (#952)
21a17b303 is described below
commit 21a17b303478cb3b172b7cda16cddaf524ef02b8
Author: Lewis John McGibbney <[email protected]>
AuthorDate: Sun Aug 16 13:47:49 2026 -0600
Trigger single-node Hadoop smoke test from PR /smoke-test comments and
release tags (#952)
---
.github/workflows/jenkins-smoke-test.yml | 161 ++++++++++++++++++++++++++++++
Jenkinsfile.smoke-test-single-node-hadoop | 130 +++++++++++++++++++++++-
2 files changed, 288 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/jenkins-smoke-test.yml
b/.github/workflows/jenkins-smoke-test.yml
new file mode 100644
index 000000000..c906f6288
--- /dev/null
+++ b/.github/workflows/jenkins-smoke-test.yml
@@ -0,0 +1,161 @@
+# 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.
+#
+# Remotely trigger the ASF Jenkins single-node Hadoop smoke job.
+# - PR comment (created only): a line that is exactly "/smoke-test" or
+# "/smoke-test hadoop=<ver>" from a user with write/maintain/admin.
+# - Tag push: release-<major>.<minor> or release-<major>.<minor>.<patch>
+# Jenkins posts/updates the PR ACK comment; this workflow only triggers.
+# Operator setup (secrets, Jenkins remote trigger): Nutch wiki.
+
+name: Jenkins smoke test trigger
+
+on:
+ issue_comment:
+ types: [created]
+ push:
+ tags:
+ - 'release-*'
+
+permissions:
+ contents: read
+ pull-requests: read
+
+jobs:
+ trigger-smoke-test:
+ runs-on: ubuntu-latest
+ # GitHub cannot filter issue_comment events by body under "on:", so it
records
+ # a (skipped) workflow run for every comment. This job-level guard ensures
a
+ # runner only starts for tag pushes or PR comments that mention
/smoke-test;
+ # the step below still enforces the strict whole-line command grammar.
+ # Plain issues and unrelated comments are ignored.
+ if: >
+ github.event_name == 'push' ||
+ (github.event_name == 'issue_comment' &&
+ github.event.issue.pull_request &&
+ contains(github.event.comment.body, '/smoke-test'))
+ steps:
+ - name: Resolve trigger parameters
+ id: params
+ env:
+ EVENT_NAME: ${{ github.event_name }}
+ COMMENT_BODY: ${{ github.event.comment.body }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ TAG_NAME: ${{ github.ref_name }}
+ ACTOR: ${{ github.actor }}
+ REPOSITORY: ${{ github.repository }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ set -euo pipefail
+
+ should_trigger=false
+ git_ref=""
+ hadoop_version=""
+
+ if [ "${EVENT_NAME}" = "push" ]; then
+ if [[ "${TAG_NAME}" =~ ^release-[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
+ should_trigger=true
+ git_ref="${TAG_NAME}"
+ echo "Tag push matched release version pattern: ${TAG_NAME}"
+ else
+ echo "Skipping tag '${TAG_NAME}' (not release-X.Y or
release-X.Y.Z)."
+ fi
+ else
+ # Case-sensitive whole-line command match.
+ while IFS= read -r line || [ -n "${line}" ]; do
+ if [[ "${line}" =~ ^/smoke-test$ ]]; then
+ should_trigger=true
+ git_ref="pr/${ISSUE_NUMBER}"
+ break
+ elif [[ "${line}" =~
^/smoke-test[[:space:]]+hadoop=([A-Za-z0-9._-]+)$ ]]; then
+ should_trigger=true
+ git_ref="pr/${ISSUE_NUMBER}"
+ hadoop_version="${BASH_REMATCH[1]}"
+ break
+ fi
+ done <<< "${COMMENT_BODY}"
+
+ if [ "${should_trigger}" != true ]; then
+ echo "Comment is not a /smoke-test command; nothing to do."
+ else
+ echo "Checking write permission for ${ACTOR} on ${REPOSITORY}..."
+ perm="$(gh api
"repos/${REPOSITORY}/collaborators/${ACTOR}/permission" --jq .permission)"
+ case "${perm}" in
+ admin|maintain|write)
+ echo "Actor permission=${perm}; allowed."
+ ;;
+ *)
+ echo "Actor permission=${perm}; refusing /smoke-test (need
write, maintain, or admin)."
+ should_trigger=false
+ git_ref=""
+ hadoop_version=""
+ ;;
+ esac
+ fi
+ fi
+
+ echo "should_trigger=${should_trigger}" >> "${GITHUB_OUTPUT}"
+ echo "git_ref=${git_ref}" >> "${GITHUB_OUTPUT}"
+ echo "hadoop_version=${hadoop_version}" >> "${GITHUB_OUTPUT}"
+
+ - name: Trigger Jenkins smoke job
+ if: steps.params.outputs.should_trigger == 'true'
+ env:
+ JENKINS_SMOKE_USER: ${{ secrets.JENKINS_SMOKE_USER }}
+ JENKINS_SMOKE_TOKEN: ${{ secrets.JENKINS_SMOKE_TOKEN }}
+ GIT_REF: ${{ steps.params.outputs.git_ref }}
+ HADOOP_VERSION: ${{ steps.params.outputs.hadoop_version }}
+ run: |
+ set -euo pipefail
+
+ if [ -z "${JENKINS_SMOKE_USER}" ] || [ -z "${JENKINS_SMOKE_TOKEN}"
]; then
+ echo "Missing secrets JENKINS_SMOKE_USER and/or
JENKINS_SMOKE_TOKEN."
+ exit 1
+ fi
+
+ JENKINS_URL="https://ci-builds.apache.org"
+
JOB_URL="${JENKINS_URL}/job/Nutch/job/Nutch-Smoke-Test-Single-Node-Hadoop-Cluster"
+
+ echo "Requesting Jenkins crumb..."
+ crumb_json="$(curl -sS -u
"${JENKINS_SMOKE_USER}:${JENKINS_SMOKE_TOKEN}" \
+ "${JENKINS_URL}/crumbIssuer/api/json")"
+ crumb_field="$(jq -r '.crumbRequestField' <<<"${crumb_json}")"
+ crumb="$(jq -r '.crumb' <<<"${crumb_json}")"
+ if [ -z "${crumb_field}" ] || [ "${crumb_field}" = "null" ] \
+ || [ -z "${crumb}" ] || [ "${crumb}" = "null" ]; then
+ echo "Failed to obtain Jenkins crumb: ${crumb_json}"
+ exit 1
+ fi
+
+ echo "Triggering ${JOB_URL}/buildWithParameters GIT_REF=${GIT_REF}" \
+ "${HADOOP_VERSION:+HADOOP_VERSION=${HADOOP_VERSION}}"
+
+ curl_args=(
+ -sS -f
+ -u "${JENKINS_SMOKE_USER}:${JENKINS_SMOKE_TOKEN}"
+ -H "${crumb_field}:${crumb}"
+ -X POST
+ "${JOB_URL}/buildWithParameters"
+ --data-urlencode "GIT_REF=${GIT_REF}"
+ --data-urlencode "EMAIL_RECIPIENT="
+ )
+ if [ -n "${HADOOP_VERSION}" ]; then
+ curl_args+=(--data-urlencode "HADOOP_VERSION=${HADOOP_VERSION}")
+ fi
+
+ curl "${curl_args[@]}"
+ echo
+ echo "Jenkins build queued successfully."
+ echo "Job: ${JOB_URL}/"
diff --git a/Jenkinsfile.smoke-test-single-node-hadoop
b/Jenkinsfile.smoke-test-single-node-hadoop
index 845871769..95b10a052 100644
--- a/Jenkinsfile.smoke-test-single-node-hadoop
+++ b/Jenkinsfile.smoke-test-single-node-hadoop
@@ -24,10 +24,132 @@
// PRs: pr/866 (fetches pull/866/head; do not pass a bare
number)
// Blank/whitespace GIT_REF is treated as master.
// EMAIL_RECIPIENT — optional job-result notification address
-// HADOOP_VERSION — Hadoop tarball version to download (default: 3.4.2)
+// HADOOP_VERSION — Hadoop tarball version to download (default: 3.5.0)
//
-// Paste this script into the ASF Jenkins job configure page when updating the
live job.
-// Landing Jenkinsfile(s) in the apache/nutch git repo is tracked separately.
+// Also triggered remotely by GitHub Actions
(.github/workflows/jenkins-smoke-test.yml):
+// - PR comment "/smoke-test" or "/smoke-test hadoop=<ver>" (write access;
created only)
+// → GIT_REF=pr/<n>, optional HADOOP_VERSION; EMAIL_RECIPIENT left empty
+// - Tag push matching release-X.Y or release-X.Y.Z → GIT_REF=<tag>
+// Enable remote/API triggering on the live job and configure GHA secrets
+// JENKINS_SMOKE_USER / JENKINS_SMOKE_TOKEN (operator details: Nutch wiki).
+//
+// When GIT_REF is pr/<n>, this pipeline creates one GitHub PR comment (ACK)
and
+// updates that same comment with the final result. Requires Jenkins credential
+// id "nutch-github-pr-comment" (secret text: GitHub token with PR comment
rights).
+//
+// The ASF Jenkins job loads this file from SCM (apache/nutch). Merging changes
+// to this Jenkinsfile on the configured branch is enough; do not paste the
+// script into the job configure page.
+
+import groovy.json.JsonOutput
+import groovy.json.JsonSlurper
+
+/** Build the markdown body for the GitHub PR ACK / result comment. */
+String nutchSmokePrCommentBody(String statusEmoji, String statusText, String
durationText) {
+ def gitRef = env.RESOLVED_GIT_REF ?: params.GIT_REF ?: 'master'
+ def lines = [
+ "🧪 Nutch smoke test — ${statusEmoji} ${statusText}",
+ "🌿 GIT_REF: `${gitRef}`",
+ "🐘 Hadoop: `${params.HADOOP_VERSION}`",
+ "🏷️ Build: #${env.BUILD_NUMBER}",
+ ]
+ if (durationText) {
+ lines.add("⏱️ ${durationText}")
+ }
+ lines.add("🔗 ${env.BUILD_URL}")
+ return lines.join('\n')
+}
+
+@NonCPS
+String nutchSmokeParseGithubCommentId(String response) {
+ def json = new JsonSlurper().parseText(response)
+ return json?.id?.toString()
+}
+
+/**
+ * Create or update a GitHub issue/PR comment via REST.
+ * credentialsId nutch-github-pr-comment must be a secret-text GitHub token.
+ * Returns the comment id as String, or null on failure (non-fatal).
+ */
+String nutchSmokeGithubPrComment(String method, String urlPath, String body) {
+ try {
+ def commentId = null
+ withCredentials([string(credentialsId: 'nutch-github-pr-comment',
variable: 'GITHUB_TOKEN')]) {
+ def payload = JsonOutput.toJson([body: body])
+ writeFile file: 'nutch-smoke-github-comment.json', text: payload
+ def response = sh(
+ script: """#!/bin/bash
+ set -euo pipefail
+ curl -sS -X '${method}' \\
+ -H "Authorization: Bearer \${GITHUB_TOKEN}" \\
+ -H "Accept: application/vnd.github+json" \\
+ -H "X-GitHub-Api-Version: 2022-11-28" \\
+ -H "Content-Type: application/json" \\
+ --data @nutch-smoke-github-comment.json \\
+ "https://api.github.com/repos/apache/nutch${urlPath}"
+ """,
+ returnStdout: true
+ ).trim()
+ commentId = nutchSmokeParseGithubCommentId(response)
+ if (!commentId) {
+ echo "GitHub PR comment ${method} did not return an id:
${response}"
+ }
+ }
+ return commentId
+ } catch (Exception e) {
+ echo "GitHub PR comment ${method} failed (continuing job): ${e}"
+ return null
+ }
+}
+
+void nutchSmokePostPrAckComment() {
+ def gitRef = env.RESOLVED_GIT_REF ?: ''
+ if (!(gitRef ==~ /^pr\/[0-9]+$/)) {
+ return
+ }
+ env.GITHUB_PR_ID = gitRef.substring(3)
+ def body = nutchSmokePrCommentBody('🚀', 'running', null)
+ def commentId = nutchSmokeGithubPrComment(
+ 'POST',
+ "/issues/${env.GITHUB_PR_ID}/comments",
+ body
+ )
+ if (commentId) {
+ env.GITHUB_PR_COMMENT_ID = commentId
+ echo "Posted GitHub PR ACK comment id=${commentId} on PR
#${env.GITHUB_PR_ID}"
+ }
+}
+
+void nutchSmokeUpdatePrResultComment() {
+ if (!env.GITHUB_PR_COMMENT_ID) {
+ return
+ }
+ def result = currentBuild.currentResult ?: 'SUCCESS'
+ def statusEmoji
+ def statusText
+ switch (result) {
+ case 'SUCCESS':
+ statusEmoji = '✅'
+ statusText = 'SUCCESS'
+ break
+ case 'ABORTED':
+ statusEmoji = '⏹️'
+ statusText = 'ABORTED'
+ break
+ default:
+ statusEmoji = '❌'
+ statusText = result
+ break
+ }
+ def durationText = currentBuild.durationString?.replace(' and counting',
'')?.trim()
+ def body = nutchSmokePrCommentBody(statusEmoji, statusText, durationText)
+ nutchSmokeGithubPrComment(
+ 'PATCH',
+ "/issues/comments/${env.GITHUB_PR_COMMENT_ID}",
+ body
+ )
+ echo "Updated GitHub PR comment id=${env.GITHUB_PR_COMMENT_ID} with
result=${result}"
+}
pipeline {
agent { label 'ubuntu' }
@@ -66,6 +188,7 @@ pipeline {
currentBuild.displayName = "#${env.BUILD_NUMBER} ·
${gitRef}"
currentBuild.description = "Git Ref: ${gitRef}"
echo "Resolved GIT_REF=${gitRef}"
+ nutchSmokePostPrAckComment()
}
}
}
@@ -301,6 +424,7 @@ pipeline {
post {
always {
script {
+ nutchSmokeUpdatePrResultComment()
if (params.EMAIL_RECIPIENT != '') {
emailext (
to: params.EMAIL_RECIPIENT,