Skip to content

atr-dist-tooling-atr-maven-plugin-1.0.0-beta-1-e3a6745a-c1f2-45a7-9e2b-7dcfef35d928 #1

atr-dist-tooling-atr-maven-plugin-1.0.0-beta-1-e3a6745a-c1f2-45a7-9e2b-7dcfef35d928

atr-dist-tooling-atr-maven-plugin-1.0.0-beta-1-e3a6745a-c1f2-45a7-9e2b-7dcfef35d928 #1

# 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.
name: Distribute from ATR to Maven Central
run-name: "${{ inputs.atr-id }}"
on:
workflow_dispatch:
inputs:
atr-id:
description: 'Run ID (from ATR)'
required: true
asf-uid:
description: 'Originating user ID'
required: true
task_id:
description: 'Originating ATR Task ID'
required: true
project:
description: 'Project in ATR to pull files from'
required: true
version:
description: 'Version in ATR to pull files from'
required: true
phase:
description: 'Expected release phase in ATR'
required: true
distribution-owner-namespace:
description: "Owner namespace"
required: true
distribution-package:
description: 'Package/project name in ATR'
required: true
distribution-version:
description: 'Distribution version'
required: true
jobs:
distribute:
permissions:
id-token: write
contents: read
runs-on: ubuntu-latest
env:
ATR_HOST: releases.apache.org
SSH_PORT: 2222
WORKFLOW: distribute-mavencentral.yml
NJORD_STORE: atr-deployment-${{ inputs.distribution-package }}-${{ inputs.distribution-version }}
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
TAG_NAME: "${{ inputs.distribution-owner-namespace }}-${{ inputs.distribution-package }}-${{ inputs.distribution-version }}"
steps:
- name: Sleep for 5 seconds
run: sleep 5s
shell: bash
- name: Create settings.xml
run: |
mkdir -p ~/.m2
cat > ~/.m2/settings.xml << EOF
<?xml version="1.0" encoding="UTF-8"?>
<settings>
<pluginGroups>
<pluginGroup>eu.maveniverse.maven.plugins</pluginGroup>
</pluginGroups>
<servers>
<server>
<id>sonatype-cp</id>
<username>${CENTRAL_USERNAME}</username>
<password>${CENTRAL_PASSWORD}</password>
<configuration>
<njord.publisher>sonatype-cp</njord.publisher>
</configuration>
</server>
</servers>
</settings>
EOF
- name: Create extensions.xml
run: |
cat > ~/.m2/extensions.xml << EOF
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<extension>
<groupId>eu.maveniverse.maven.plugins</groupId>
<artifactId>njord-maven-plugin</artifactId>
<version>${currentVersion}</version>
</extension>
</extensions>
EOF
- name: Set up JDK 17
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961
with:
java-version: '17'
distribution: 'temurin'
overwrite-settings: false
- name: Set up Maven 3.9+
uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1
with:
maven-version: 3.9.12
- name: Create a GitHub OIDC JWT
id: create-github-jwt
shell: bash
run: |
set -euo pipefail
url="${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=https://${ATR_HOST}/"
jwt="$(curl -sS --fail-with-body -H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" "$url" | jq -r .value)"
echo "::add-mask::$jwt"
echo "jwt=$jwt" >> "$GITHUB_OUTPUT"
- name: Generate an ephemeral SSH key
id: generate-ssh-key
shell: bash
run: |
set -euxo pipefail
ssh-keygen -t ed25519 -N "" -f "$RUNNER_TEMP/ssh_key"
echo "ssh_private_key_path=$RUNNER_TEMP/ssh_key" >> "$GITHUB_OUTPUT"
echo "ssh_public_key=$(cat "$RUNNER_TEMP/ssh_key.pub")" >> "$GITHUB_OUTPUT"
- name: Register the ephemeral SSH key with ATR
shell: bash
run: |
set -euxo pipefail
case "${ATR_HOST}" in
*.apache.org) ;;
*) echo "atr-host must match *.apache.org"; exit 1;;
esac
jq -n --arg publisher github \
--arg jwt "$JWT" \
--arg key "$SSH_PUBLIC_KEY" \
--arg uid "$INPUTS_ASF_UID" \
--arg phase "$INPUTS_PHASE" \
--arg project_key "$INPUTS_PROJECT" \
--arg version "$INPUTS_VERSION" \
--arg task_id "$INPUTS_TASK_ID" \
'{publisher:$publisher, jwt:$jwt, ssh_key:$key, asf_uid:$uid, project_key:$project_key, phase:$phase, version:$version, task_id:$task_id}' |
curl -sS --fail-with-body -X POST -H 'Content-Type: application/json' -d @- \
"https://${ATR_HOST}/api/distribute/ssh/register"
env:
JWT: ${{ steps.create-github-jwt.outputs.jwt }}
SSH_PUBLIC_KEY: ${{ steps.generate-ssh-key.outputs.ssh_public_key }}
INPUTS_PROJECT: ${{ inputs.project }}
INPUTS_VERSION: ${{ inputs.version }}
INPUTS_PHASE: ${{ inputs.phase }}
INPUTS_ASF_UID: ${{ inputs.asf-uid }}
INPUTS_TASK_ID: ${{ inputs.task_id }}
- name: Report status back to ATR
shell: bash
run: |
set -euxo pipefail
jq -n --arg publisher github \
--arg jwt "$JWT" \
--arg workflow "$WORKFLOW" \
--arg run_id $RUN_ID \
--arg project_key "$INPUTS_PROJECT" \
'{publisher:$publisher, jwt:$jwt, workflow:$workflow, run_id:$run_id, project_key:$project_key, status:"in_progress", message:"Compiling distribution"}' |
curl -sS --fail-with-body -X POST -H 'Content-Type: application/json' -d @- \
"https://${ATR_HOST}/api/distribute/task/status"
env:
JWT: ${{ steps.create-github-jwt.outputs.jwt }}
RUN_ID: ${{ github.run_id }}
INPUTS_PROJECT: ${{ inputs.project }}
- name: Download from ATR using rsync
shell: bash
run: |
set -euxo pipefail
if ! [[ "${SSH_PORT}" =~ ^[0-9]+$ ]]
then
echo "::error::ssh-port must be an integer"
exit 1
fi
command -v rsync > /dev/null || { sudo apt-get update -y && sudo apt-get install -y rsync; }
mkdir stg
rsync -av \
-e "ssh -p 2222 -i $SSH_PRIVATE_KEY_PATH -o StrictHostKeyChecking=accept-new" \
-- \
"github@${ATR_HOST}:/${INPUTS_PROJECT}/${INPUTS_VERSION}/maven/" ./stg/
env:
INPUTS_PROJECT: ${{ inputs.project }}
INPUTS_VERSION: ${{ inputs.version }}
# INPUTS_DETAILS: ${{ inputs.details }}
SSH_PRIVATE_KEY_PATH: ${{ steps.generate-ssh-key.outputs.ssh_private_key_path }}
- name: Get store ID and publish
shell: bash
run: |
set -euxo pipefail
echo "Importing Njord bundle"
NJORD_STORE=$(mvn njord:import-all -Dnjord.dir=./stg | sed -En 's/.*Imported to (.+) staged.*/\1/p')
echo "Validating store: $NJORD_STORE"
mvn njord:validate -Dnjord.store=$NJORD_STORE -Dnjord.publisher=sonatype-cp -Dnjord.details=true -q | sed -n '/Central Requirements/,/ArtifactStore.*failed validation/{/ArtifactStore.*failed validation/!s/^\[ERROR\] *//p}' | tee .err
echo "Publishing store: $NJORD_STORE"
mvn njord:publish -Dnjord.store=$NJORD_STORE -Dnjord.publisher=sonatype-cp -Dnjord.publishingType=automatic 2>&1 | tee publish.log
mvn njord:drop -Dnjord.store=$NJORD_STORE
DEPLOYMENT_ID=$(grep -oP 'Deployment ID:\s*\K\S+' publish.log || true)
echo "MAVEN_DEPLOYMENT_ID=$DEPLOYMENT_ID" >> "$GITHUB_ENV"
- name: Report status back to ATR
shell: bash
run: |
set -euxo pipefail
jq -n --arg publisher github \
--arg jwt "$JWT" \
--arg workflow "$WORKFLOW" \
--arg run_id $RUN_ID \
--arg project_key "$INPUTS_PROJECT" \
'{publisher:$publisher, jwt:$jwt, workflow:$workflow, run_id:$run_id, project_key:$project_key, status:"in_progress", message:"Verifying distribution status"}' |
curl -sS --fail-with-body -X POST -H 'Content-Type: application/json' -d @- \
"https://${ATR_HOST}/api/distribute/task/status"
env:
JWT: ${{ steps.create-github-jwt.outputs.jwt }}
RUN_ID: ${{ github.run_id }}
INPUTS_PROJECT: ${{ inputs.project }}
- name: Verify Maven Central deployment
shell: bash
run: |
set -euo pipefail
if [ -z "$MAVEN_DEPLOYMENT_ID" ]; then
echo "::warning::No Deployment ID found; skipping verification"
exit 0
fi
echo "Polling Maven Central for deployment ID: $MAVEN_DEPLOYMENT_ID"
for i in $(seq 1 3); do
STATUS_JSON=$(curl -sS -X POST --fail-with-body \
-u "$CENTRAL_USERNAME:$CENTRAL_PASSWORD" \
"https://central.sonatype.com/api/v1/publisher/status?id=$MAVEN_DEPLOYMENT_ID")
DEPLOYMENT_STATE=$(echo "$STATUS_JSON" | jq -r '.deploymentState // empty')
ERRORS=$(echo "$STATUS_JSON" | jq -r '.errors // empty')
echo "Attempt $i: deployment state is '$DEPLOYMENT_STATE'"
case "$DEPLOYMENT_STATE" in
PUBLISHED)
echo "Deployment $MAVEN_DEPLOYMENT_ID confirmed PUBLISHED"
exit 0
;;
FAILED)
echo "::error::Deployment $MAVEN_DEPLOYMENT_ID FAILED at Maven Central"
echo $ERRORS > .err
exit 1
;;
VALIDATED|PUBLISHING|PENDING)
sleep 10
;;
*)
echo "::warning::Unexpected deployment state '$DEPLOYMENT_STATE'"
exit 0
;;
esac
done
echo "Deployment still in state '$DEPLOYMENT_STATE' after 30s; continuing"
- name: Report status back to ATR
shell: bash
run: |
set -euxo pipefail
jq -n --arg publisher github \
--arg jwt "$JWT" \
--arg workflow "$WORKFLOW" \
--arg run_id $RUN_ID \
--arg project_key "$INPUTS_PROJECT" \
'{publisher:$publisher, jwt:$jwt, workflow:$workflow, run_id:$run_id, project_key:$project_key, status:"in_progress", message:"Recording distribution"}' |
curl -sS --fail-with-body -X POST -H 'Content-Type: application/json' -d @- \
"https://${ATR_HOST}/api/distribute/task/status"
env:
JWT: ${{ steps.create-github-jwt.outputs.jwt }}
RUN_ID: ${{ github.run_id }}
INPUTS_PROJECT: ${{ inputs.project }}
- name: Record distribution on ATR
shell: bash
run: |
set -euxo pipefail
DETAILS_JSON=false
[ "${INPUTS_DETAILS}" = "true" ] && DETAILS_JSON=true
jq -n --arg publisher github \
--arg jwt "$JWT" \
--arg uid "$INPUTS_ASF_UID" \
--arg project "$INPUTS_PROJECT" \
--arg version "$INPUTS_VERSION" \
--arg phase "$INPUTS_PHASE" \
--arg platform "MAVEN" \
--arg distribution_owner_namespace "$INPUTS_DISTRIBUTION_OWNER_NAMESPACE" \
--arg distribution_package "$INPUTS_DISTRIBUTION_PACKAGE" \
--arg distribution_version "$INPUTS_DISTRIBUTION_VERSION" \
--arg task_id "$INPUTS_TASK_ID" \
--argjson details "$DETAILS_JSON" \
'{publisher:$publisher, jwt:$jwt, asf_uid:$uid, project:$project, version:$version, phase:$phase, platform:$platform, distribution_owner_namespace:$distribution_owner_namespace, distribution_package:$distribution_package, distribution_version:$distribution_version, staging:false, details:$details, task_id:$task_id}' |
curl -sS --fail-with-body -X POST -H 'Content-Type: application/json' -d @- \
"https://${ATR_HOST}/api/distribute/record_from_workflow"
env:
INPUTS_PROJECT: ${{ inputs.project }}
INPUTS_VERSION: ${{ inputs.version }}
INPUTS_PHASE: ${{ inputs.phase }}
INPUTS_ASF_UID: ${{ inputs.asf-uid }}
INPUTS_DISTRIBUTION_OWNER_NAMESPACE: ${{ inputs.distribution-owner-namespace }}
INPUTS_DISTRIBUTION_PACKAGE: ${{ inputs.distribution-package }}
INPUTS_DISTRIBUTION_VERSION: ${{ inputs.distribution-version }}
INPUTS_TASK_ID: ${{ inputs.task_id }}
INPUTS_DETAILS: "false"
JWT: ${{ steps.create-github-jwt.outputs.jwt }}
- name: Report status back to ATR
shell: bash
if: failure()
run: |
set -euxo pipefail
jq -n --arg publisher github \
--arg jwt "$JWT" \
--arg workflow "$WORKFLOW" \
--arg run_id $RUN_ID \
--arg project_key "$INPUTS_PROJECT" \
--arg err "$([ -f .err ] && [ -s .err ] && echo "Build failed: $(cat .err)" || echo "Github workflow failed")" \
--arg status "failed" \
'{publisher:$publisher, jwt:$jwt, workflow:$workflow, run_id:$run_id, project_key:$project_key, status:$status, message:$err}' |
curl -sS --fail-with-body -X POST -H 'Content-Type: application/json' -d @- \
"https://${ATR_HOST}/api/distribute/task/status"
env:
JWT: ${{ steps.create-github-jwt.outputs.jwt }}
RUN_ID: ${{ github.run_id }}
STATUS: ${{ job.status }}
INPUTS_PROJECT: ${{ inputs.project }}
- name: Report status back to ATR
shell: bash
if: success()
run: |
set -euxo pipefail
jq -n --arg publisher github \
--arg jwt "$JWT" \
--arg workflow "$WORKFLOW" \
--arg run_id $RUN_ID \
--arg project_key "$INPUTS_PROJECT" \
--arg status "success" \
'{publisher:$publisher, jwt:$jwt, workflow:$workflow, run_id:$run_id, project_key:$project_key, status:$status, message:"GitHub workflow succeeded"}' |
curl -sS --fail-with-body -X POST -H 'Content-Type: application/json' -d @- \
"https://${ATR_HOST}/api/distribute/task/status"
env:
JWT: ${{ steps.create-github-jwt.outputs.jwt }}
RUN_ID: ${{ github.run_id }}
STATUS: ${{ job.status }}
INPUTS_PROJECT: ${{ inputs.project }}