name: Build and Publish Docker Image
on:
push:
branches:
- main
- develop
tags:
- 'v*'
pull_request:
branches:
- main
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
security-events: write # Required for uploading SARIF results
actions: read # Required for some security scanning features
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
VCS_REF=${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Wait for image to be available
if: github.event_name != 'pull_request'
run: |
echo "Waiting for image to be available in registry..."
sleep 10
- name: Run security scan
if: github.event_name != 'pull_request'
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
if: github.event_name != 'pull_request'
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
continue-on-error: true # Don't fail the build if SARIF upload fails
test-docker-image:
runs-on: ubuntu-latest
needs: build-and-push
if: github.event_name != 'pull_request'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Wait for image propagation and test availability
run: |
echo "Waiting for image to propagate in registry..."
sleep 30
# Test if image is available by trying to pull it with retries
IMAGE_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
IMAGE_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}"
fi
echo "Testing image availability: $IMAGE_TAG"
for i in {1..5}; do
if docker pull $IMAGE_TAG; then
echo "Image successfully pulled!"
break
else
echo "Attempt $i failed, waiting 15 seconds..."
sleep 15
fi
done
- name: Test Docker image
run: |
# Determine the correct image tag
IMAGE_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
IMAGE_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}"
fi
echo "Testing image: $IMAGE_TAG"
# Test basic functionality
docker run --rm \
$IMAGE_TAG \
python -c "from propublica_mcp.server import search_nonprofits; print('Import successful')"
# Test the CLI help
docker run --rm \
$IMAGE_TAG \
python -m propublica_mcp.server --help
- name: Test with docker-compose
run: |
# Determine the correct image tag for docker-compose
IMAGE_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
IMAGE_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}"
fi
# Create minimal docker-compose for testing
cat > docker-compose.test.yml << EOF
version: '3.8'
services:
propublica-mcp:
image: $IMAGE_TAG
command: python -c "import propublica_mcp.server; print('Docker Compose test successful')"
EOF
# Run the test
docker compose -f docker-compose.test.yml run --rm propublica-mcp
create-release:
runs-on: ubuntu-latest
needs: [build-and-push, test-docker-image]
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
actions: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Extract version from tag
id: extract_version
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "Extracted version: $VERSION"
- name: Extract release notes from CHANGELOG.md
id: extract_release_notes
run: |
# Extract the release notes for this version from CHANGELOG.md
version="${{ steps.extract_version.outputs.VERSION }}"
echo "Extracting release notes for version $version"
# Find the section for this version and extract until the next version or end
awk -v version="$version" '
BEGIN { found=0; content="" }
/^## \[.*\] - [0-9]{4}-[0-9]{2}-[0-9]{2}/ {
if (found) exit
# Extract version from [x.x.x] format
match($0, /\[([^\]]+)\]/, arr)
if (arr[1] == version) found=1
next
}
found && /^## \[.*\] - [0-9]{4}-[0-9]{2}-[0-9]{2}/ { exit }
found && !/^## / {
if (content == "" && $0 == "") next # Skip leading empty lines
content = content $0 "\n"
}
END {
# Remove trailing newlines
gsub(/\n+$/, "", content)
print content
}' CHANGELOG.md > release-notes.md
echo "Release notes extracted to release-notes.md"
echo "Release notes content:"
cat release-notes.md
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ steps.extract_version.outputs.VERSION }}
name: "v${{ steps.extract_version.outputs.VERSION }}: ProPublica MCP Server"
body_path: release-notes.md
draft: false
prerelease: false
- name: Trigger DXT Package Build
uses: actions/github-script@v7
with:
script: |
github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'dxt-release.yml',
ref: 'main',
inputs: {
tag: 'v${{ steps.extract_version.outputs.VERSION }}'
}
});