Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions .github/workflows/atr-release-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,11 @@ jobs:
fetch-depth: 0 # Full history for proper tagging

- name: Create test tag if not exists
env:
RELEASE_VERSION: ${{ inputs.release_version }}
RELEASE_CANDIDATE: ${{ inputs.release_candidate }}
run: |
TEST_TAG="v${{ inputs.release_version }}-${{ inputs.release_candidate }}"
TEST_TAG="v${RELEASE_VERSION}-${RELEASE_CANDIDATE}"
if ! git rev-parse "${TEST_TAG}" >/dev/null 2>&1; then
echo "Creating test tag: ${TEST_TAG}"
git config user.email "test@example.com"
Expand Down Expand Up @@ -221,7 +224,7 @@ jobs:

- name: Upload artifacts to ATR (Real)
if: ${{ !inputs.dry_run }}
uses: apache/tooling-actions/upload-to-atr@main
uses: apache/tooling-actions/upload-to-atr@81f0a6cf2202b362ee6434b82c271b1849133581 # main @ 2026-07-27
with:
project: ${{ env.ATR_PROJECT_NAME }}
version: ${{ inputs.release_version }}-${{ inputs.release_candidate }}
Expand Down Expand Up @@ -261,7 +264,9 @@ jobs:
name: Vote Instructions (TEST)
runs-on: ubuntu-latest
needs: [ compose-release ]
if: ${{ !inputs.skip_vote && (success() || inputs.skip_compose) }}
# A skipped `needs` job skips its dependents unless the if: uses always().
# Without it the skip_compose=true path could never reach this job.
if: ${{ always() && !inputs.skip_vote && (needs.compose-release.result == 'success' || inputs.skip_compose) }}

steps:
- name: Generate test vote email
Expand Down Expand Up @@ -307,6 +312,10 @@ jobs:
finish-release:
name: Finalize Release (TEST)
runs-on: ubuntu-latest
# Must wait for the compose and vote jobs even though both may be skipped
# (skip_compose / skip_vote). always() keeps this reachable in that case,
# but without needs: it would otherwise start in parallel with them.
needs: [ compose-release, vote-instructions ]
if: ${{ always() && inputs.skip_vote }}

steps:
Expand Down Expand Up @@ -343,7 +352,7 @@ jobs:

- name: Resolve vote and announce on ATR (Real)
if: ${{ !inputs.dry_run }}
uses: apache/tooling-actions/release-on-atr@main
uses: apache/tooling-actions/release-on-atr@81f0a6cf2202b362ee6434b82c271b1849133581 # main @ 2026-07-27
with:
version: ${{ inputs.release_version }}
atr-host: release-test.apache.org
Expand Down Expand Up @@ -382,7 +391,7 @@ jobs:

# Test main platform
echo "1. Testing main platform (https://release-test.apache.org):"
if curl -s -o /dev/null -w " HTTP Status: %{http_code}\n" https://release-test.apache.org; then
if curl -fsS --max-time 15 -o /dev/null -w " HTTP Status: %{http_code}\n" https://release-test.apache.org; then
echo " ✅ Platform is reachable"
else
echo " ❌ Platform unreachable"
Expand Down
26 changes: 18 additions & 8 deletions .github/workflows/atr-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,13 @@ jobs:
fetch-depth: 0 # Full history for proper tagging

- name: Verify release tag
env:
RELEASE_VERSION: ${{ inputs.release_version }}
RELEASE_CANDIDATE: ${{ inputs.release_candidate }}
run: |
if ! git rev-parse "v${{ inputs.release_version }}-${{ inputs.release_candidate }}" >/dev/null 2>&1; then
echo "ERROR: Release tag not found: v${{ inputs.release_version }}-${{ inputs.release_candidate }}"
RELEASE_TAG="v${RELEASE_VERSION}-${RELEASE_CANDIDATE}"
if ! git rev-parse "${RELEASE_TAG}" >/dev/null 2>&1; then
echo "ERROR: Release tag not found: ${RELEASE_TAG}"
exit 1
fi
echo "✓ Release tag verified"
Expand Down Expand Up @@ -236,7 +240,7 @@ jobs:
# gpg --armor --detach-sign build/distributions/*.jar

- name: Upload artifacts to ATR
uses: apache/tooling-actions/upload-to-atr@main
uses: apache/tooling-actions/upload-to-atr@81f0a6cf2202b362ee6434b82c271b1849133581 # main @ 2026-07-27
with:
asf-uid: ${{ secrets.ASF_USERNAME }}
project: ${{ env.ATR_PROJECT_NAME }}
Expand All @@ -246,7 +250,7 @@ jobs:
ssh-port: 2222

- name: Upload artifacts for review
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # 6.0.0
with:
name: release-artifacts-${{ inputs.release_version }}-${{ inputs.release_candidate }}
path: build/distributions/
Expand Down Expand Up @@ -274,7 +278,9 @@ jobs:
name: Vote Instructions
runs-on: ubuntu-latest
needs: [ compose-release ]
if: ${{ !inputs.skip_vote && (success() || inputs.skip_compose) }}
# A skipped `needs` job skips its dependents unless the if: uses always().
# Without it the skip_compose=true path could never reach this job.
if: ${{ always() && !inputs.skip_vote && (needs.compose-release.result == 'success' || inputs.skip_compose) }}

steps:
- name: Generate vote email template
Expand Down Expand Up @@ -329,7 +335,7 @@ jobs:
echo "**Note**: ATR voting automation is not yet available. Manual process required." >> $GITHUB_STEP_SUMMARY

- name: Upload vote email template
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # 6.0.0
with:
name: vote-email-template
path: vote-email.txt
Expand All @@ -338,6 +344,10 @@ jobs:
finish-release:
name: Finalize and Announce Release
runs-on: ubuntu-latest
# Must wait for the compose and vote jobs even though both may be skipped
# (skip_compose / skip_vote). always() keeps this reachable in that case,
# but without needs: it would otherwise start in parallel with them.
needs: [ compose-release, vote-instructions ]
if: ${{ always() && inputs.skip_vote }} # Manual trigger after vote passes

steps:
Expand All @@ -347,7 +357,7 @@ jobs:
ref: "v${{ inputs.release_version }}-${{ inputs.release_candidate }}"

- name: Resolve vote and announce on ATR
uses: apache/tooling-actions/release-on-atr@main
uses: apache/tooling-actions/release-on-atr@81f0a6cf2202b362ee6434b82c271b1849133581 # main @ 2026-07-27
with:
version: ${{ inputs.release_version }}
atr-host: release-test.apache.org
Expand Down Expand Up @@ -428,7 +438,7 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY
echo "#### Distribution:" >> $GITHUB_STEP_SUMMARY
echo "- Apache Mirrors: https://www.apache.org/dyn/closer.lua/solr/mcp/${{ inputs.release_version }}/" >> $GITHUB_STEP_SUMMARY
echo "- Docker Hub: `apache/solr-mcp:${{ inputs.release_version }}`" >> $GITHUB_STEP_SUMMARY
echo "- Docker Hub: \`apache/solr-mcp:${{ inputs.release_version }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "#### Next Steps:" >> $GITHUB_STEP_SUMMARY
echo "1. Send announcement to announce@apache.org" >> $GITHUB_STEP_SUMMARY
Expand Down
92 changes: 59 additions & 33 deletions .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ jobs:
run: |
# Ensure the Gradle project version matches the GA version (removes any -SNAPSHOT)
# This keeps image tags and any generated artifacts consistent with the voted release
sed -i "s/version = \".*\"/version = \"${RELEASE_VERSION}\"/" build.gradle.kts
sed -i "0,/^version = \".*\"/s//version = \"${RELEASE_VERSION}\"/" build.gradle.kts

- name: Build project
run: ./gradlew build
Expand All @@ -206,34 +206,34 @@ jobs:
echo "Contact INFRA for setup requirements"

- name: Build and publish to Docker Hub (apache/solr-mcp)
env:
# Jib reads these directly; passing them as -Djib.to.auth.* would put
# the token in the process argument list, visible to anything on the runner.
JIB_TO_AUTH_USERNAME: ${{ secrets.DOCKERHUB_APACHE_USERNAME }}
JIB_TO_AUTH_PASSWORD: ${{ secrets.DOCKERHUB_APACHE_TOKEN }}
RELEASE_VERSION: ${{ inputs.release_version }}
run: |
# Publish official release to apache/solr-mcp
# This requires Apache PMC credentials
if [[ -n "${{ secrets.DOCKERHUB_APACHE_USERNAME }}" ]]; then
# Build and push with multiple tags
./gradlew jib \
-Djib.to.image=apache/solr-mcp:${{ inputs.release_version }} \
-Djib.to.auth.username=${{ secrets.DOCKERHUB_APACHE_USERNAME }} \
-Djib.to.auth.password=${{ secrets.DOCKERHUB_APACHE_TOKEN }} \
-Djib.to.tags=${{ inputs.release_version }},latest

# Also tag with major and minor versions
MAJOR_VERSION=$(echo "${{ inputs.release_version }}" | cut -d. -f1)
MINOR_VERSION=$(echo "${{ inputs.release_version }}" | cut -d. -f1-2)

./gradlew jib \
-Djib.to.image=apache/solr-mcp:${MAJOR_VERSION} \
-Djib.to.auth.username=${{ secrets.DOCKERHUB_APACHE_USERNAME }} \
-Djib.to.auth.password=${{ secrets.DOCKERHUB_APACHE_TOKEN }}

./gradlew jib \
-Djib.to.image=apache/solr-mcp:${MINOR_VERSION} \
-Djib.to.auth.username=${{ secrets.DOCKERHUB_APACHE_USERNAME }} \
-Djib.to.auth.password=${{ secrets.DOCKERHUB_APACHE_TOKEN }}
else
echo "WARNING: Apache Docker Hub credentials not configured"
set -euo pipefail
# Publish official release to apache/solr-mcp. This requires Apache PMC
# credentials; a release that cannot publish must fail loudly rather than
# reporting success while shipping nothing.
if [[ -z "${JIB_TO_AUTH_USERNAME}" || -z "${JIB_TO_AUTH_PASSWORD}" ]]; then
echo "ERROR: Apache Docker Hub credentials (DOCKERHUB_APACHE_USERNAME / DOCKERHUB_APACHE_TOKEN) are not configured" >&2
exit 1
fi

# Build and push with multiple tags
./gradlew jib \
-Djib.to.image="apache/solr-mcp:${RELEASE_VERSION}" \
-Djib.to.tags="${RELEASE_VERSION},latest"

# Also tag with major and minor versions
MAJOR_VERSION="${RELEASE_VERSION%%.*}"
MINOR_VERSION="$(echo "${RELEASE_VERSION}" | cut -d. -f1-2)"

./gradlew jib -Djib.to.image="apache/solr-mcp:${MAJOR_VERSION}"
./gradlew jib -Djib.to.image="apache/solr-mcp:${MINOR_VERSION}"

- name: Build and publish to GitHub Container Registry
run: |
# Also publish to GitHub Container Registry
Expand Down Expand Up @@ -332,7 +332,6 @@ jobs:
## Vote Thread

This release was approved by the Apache Solr PMC.
${VOTE_THREAD_NOTE}
EOF

if [[ -n "${{ inputs.vote_thread_url }}" ]]; then
Expand Down Expand Up @@ -400,7 +399,7 @@ jobs:
env:
RELEASE_VERSION: ${{ inputs.release_version }}
run: |
sed -i "s/version = \".*\"/version = \"${RELEASE_VERSION}\"/" build.gradle.kts
sed -i "0,/^version = \".*\"/s//version = \"${RELEASE_VERSION}\"/" build.gradle.kts

# Inline `docker login` rather than docker/login-action: that third-party
# action is not on the ASF GitHub Actions allow-list, which fails the whole
Expand Down Expand Up @@ -515,7 +514,10 @@ jobs:
runs-on: ubuntu-latest
# Wait for both JVM (publish-docker) and native (publish-native-manifests)
# images so the MCP Registry entry references a fully-published release.
needs: [publish-docker, publish-native-manifests]
# validate-release must be listed here too: the if: below reads its output,
# and a needs.<job> reference to a job not in needs: resolves to empty,
# which would silently make this job never run.
needs: [validate-release, publish-docker, publish-native-manifests]
if: ${{ needs.validate-release.outputs.proceed == 'true' }}

# Permissions required for OIDC-based auth to the MCP Registry and read access
Expand Down Expand Up @@ -543,16 +545,40 @@ jobs:
VERSION="${{ steps.mcp_version.outputs.version }}"
# Update the top-level server version (e.g., 1.2.3)
jq --arg v "$VERSION" '.version = $v' server.json > server.json.tmp
# Update package version to match GA release (no -SNAPSHOT suffix)
jq --arg v "$VERSION" '.packages[0].version = $v' server.json.tmp > server.json
# Update EVERY package version to match the GA release. Rewriting
# only packages[0] left the other package on -SNAPSHOT, so the
# registry entry advertised an image tag that is never published.
jq --arg v "$VERSION" '.packages[].version = $v' server.json.tmp > server.json
rm server.json.tmp
# Fail if any -SNAPSHOT version survived the rewrite
if grep -q 'SNAPSHOT' server.json; then
echo "ERROR: server.json still contains a SNAPSHOT version after rewrite" >&2
cat server.json >&2
exit 1
fi
# Show the final server.json for auditing
cat server.json

# Download the MCP Publisher CLI from its latest GitHub release
# Download the MCP Publisher CLI, pinned to an explicit release and
# verified against that release's published checksum file. Piping an
# unverified `curl -L` straight into tar would silently extract a
# GitHub error page on a 404, so download, verify, then extract.
- name: Download MCP Publisher
env:
MCP_PUBLISHER_VERSION: v1.8.0
run: |
curl -L https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher-linux-amd64.tar.gz | tar xz
set -euo pipefail
base="https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}"
asset="mcp-publisher_linux_amd64.tar.gz"
checksums="registry_${MCP_PUBLISHER_VERSION#v}_checksums.txt"

curl -fsSL -o "${asset}" "${base}/${asset}"
curl -fsSL -o "${checksums}" "${base}/${checksums}"

# Abort if the archive does not match the release's published digest
grep " ${asset}\$" "${checksums}" | sha256sum -c -

tar xzf "${asset}"
chmod +x mcp-publisher
./mcp-publisher --version

Expand Down
4 changes: 2 additions & 2 deletions server.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"url": "https://github.com/apache/solr-mcp",
"source": "github"
},
"version": "1.0.0",
"version": "1.0.0-SNAPSHOT",
"packages": [
{
"registryType": "docker",
Expand Down Expand Up @@ -51,4 +51,4 @@
]
}
]
}
}