Move package-list.json update to occur before the build step for release builds, allowing IG Publisher to generate history.html correctly. Adjust post-build steps to only update package-feed.xml, and add verification for history.html generation and deployment. This ensures registry files are prepared earlier in the process.
408 lines
14 KiB
YAML
408 lines
14 KiB
YAML
name: FHIR IG CI/CD Pipeline with Version Persistence
|
||
|
||
on:
|
||
push:
|
||
branches: [ main, develop ]
|
||
tags:
|
||
- 'v*.*.*' # Trigger on version tags like v0.3.0
|
||
pull_request:
|
||
branches: [ main ]
|
||
|
||
env:
|
||
REGISTRY: git.dghs.gov.bd
|
||
IMAGE_NAME: gitadmin/bd-core-fhir-ig
|
||
|
||
jobs:
|
||
build-ig:
|
||
runs-on: ubuntu-latest
|
||
|
||
steps:
|
||
- name: Checkout repository
|
||
uses: actions/checkout@v3
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- name: Extract version from IG
|
||
id: version
|
||
run: |
|
||
# Extract version from ImplementationGuide resource
|
||
VERSION=$(grep -oP '<version value="\K[^"]+' input/bd.fhir.core.xml | head -1)
|
||
|
||
if [ -z "$VERSION" ]; then
|
||
echo "ERROR: Could not extract version from ImplementationGuide XML"
|
||
exit 1
|
||
fi
|
||
|
||
echo "Extracted version: $VERSION"
|
||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||
|
||
# Determine if this is a release build (git tag) or dev build
|
||
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
|
||
BUILD_TYPE="release"
|
||
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
|
||
|
||
# Verify tag matches IG version
|
||
if [ "$TAG_VERSION" != "$VERSION" ]; then
|
||
echo "ERROR: Git tag version ($TAG_VERSION) doesn't match IG version ($VERSION)"
|
||
exit 1
|
||
fi
|
||
else
|
||
BUILD_TYPE="dev"
|
||
fi
|
||
|
||
echo "build_type=$BUILD_TYPE" >> $GITHUB_OUTPUT
|
||
echo "Build type: $BUILD_TYPE"
|
||
|
||
# NEW STEP: Update package-list.json BEFORE build so IG Publisher can use it
|
||
- name: Pre-build package-list.json update
|
||
run: |
|
||
VERSION="${{ steps.version.outputs.version }}"
|
||
BUILD_TYPE="${{ steps.version.outputs.build_type }}"
|
||
DATE=$(date +%Y-%m-%d)
|
||
|
||
# Only update for release builds
|
||
if [ "$BUILD_TYPE" != "release" ]; then
|
||
echo "ℹ️ Dev build - skipping package-list.json pre-build update"
|
||
exit 0
|
||
fi
|
||
|
||
echo "📋 Updating package-list.json before build..."
|
||
|
||
# Check if package-list.json exists
|
||
if [ ! -f "package-list.json" ]; then
|
||
echo "⚠️ package-list.json not found in repo - history.html won't be generated"
|
||
echo "Creating minimal package-list.json for this build..."
|
||
cat > package-list.json << EOF
|
||
{
|
||
"package-id": "bd.fhir.core",
|
||
"title": "Bangladesh Core FHIR Implementation Guide",
|
||
"canonical": "https://fhir.dghs.gov.bd/core",
|
||
"introduction": "Core FHIR profiles and extensions for Bangladesh healthcare",
|
||
"list": [
|
||
{
|
||
"version": "current",
|
||
"desc": "Continuous Integration Build (latest in version control)",
|
||
"path": "https://fhir.dghs.gov.bd/core/",
|
||
"status": "ci-build",
|
||
"current": true
|
||
}
|
||
]
|
||
}
|
||
EOF
|
||
fi
|
||
|
||
# Update package-list.json with new version
|
||
python3 << 'PYEOF'
|
||
import json
|
||
import sys
|
||
|
||
version = "$VERSION"
|
||
date = "$DATE"
|
||
|
||
with open('package-list.json', 'r') as f:
|
||
pkg_list = json.load(f)
|
||
|
||
# Check if this version already exists
|
||
version_exists = any(e['version'] == version for e in pkg_list['list'])
|
||
|
||
if not version_exists:
|
||
new_entry = {
|
||
"version": version,
|
||
"date": date,
|
||
"desc": f"Release {version}",
|
||
"path": f"https://fhir.dghs.gov.bd/core/{version}/",
|
||
"status": "trial-use",
|
||
"sequence": "STU 1"
|
||
}
|
||
# Insert after 'current' entry
|
||
pkg_list['list'].insert(1, new_entry)
|
||
|
||
with open('package-list.json', 'w') as f:
|
||
json.dump(pkg_list, f, indent=2)
|
||
|
||
print(f"✅ Added version {version} to package-list.json")
|
||
else:
|
||
print(f"ℹ️ Version {version} already exists in package-list.json")
|
||
PYEOF
|
||
|
||
echo "📋 package-list.json is ready for IG Publisher"
|
||
cat package-list.json
|
||
|
||
- name: Install Docker CLI
|
||
run: |
|
||
apt-get update
|
||
apt-get install -y docker.io
|
||
docker --version
|
||
|
||
- name: Build FHIR IG
|
||
run: |
|
||
echo "Building FHIR IG version ${{ steps.version.outputs.version }}..."
|
||
|
||
CONTAINER_ID=$(docker create \
|
||
hl7fhir/ig-publisher-base:latest \
|
||
/bin/bash -c "cp -r /home/publisher/ig /tmp/build && cd /tmp/build && _updatePublisher.sh -y && _genonce.sh")
|
||
|
||
echo "Container ID: $CONTAINER_ID"
|
||
|
||
docker cp $(pwd)/. $CONTAINER_ID:/home/publisher/ig/
|
||
docker start -a $CONTAINER_ID
|
||
EXIT_CODE=$?
|
||
|
||
# Copy outputs
|
||
echo "Copying outputs from container..."
|
||
docker cp $CONTAINER_ID:/tmp/build/output ./output || echo "Warning: No output directory"
|
||
docker cp $CONTAINER_ID:/tmp/build/fsh-generated ./fsh-generated || echo "No FSH generated"
|
||
docker cp $CONTAINER_ID:/tmp/build/input-cache ./input-cache || echo "No input-cache"
|
||
docker cp $CONTAINER_ID:/tmp/build/temp ./temp || echo "No temp directory"
|
||
|
||
if [ $EXIT_CODE -ne 0 ]; then
|
||
echo "Build failed, showing logs:"
|
||
docker logs $CONTAINER_ID
|
||
docker rm $CONTAINER_ID
|
||
exit 1
|
||
fi
|
||
|
||
docker rm $CONTAINER_ID
|
||
|
||
if [ ! -f "output/index.html" ]; then
|
||
echo "ERROR: Build failed - no index.html"
|
||
exit 1
|
||
fi
|
||
|
||
# Check if history.html was generated
|
||
if [ -f "output/history.html" ]; then
|
||
echo "✅ history.html generated successfully"
|
||
else
|
||
echo "⚠️ WARNING: history.html was not generated"
|
||
echo "This usually means package-list.json was missing or invalid"
|
||
fi
|
||
|
||
echo "✅ Build successful!"
|
||
|
||
- name: Update package-feed.xml for releases
|
||
if: steps.version.outputs.build_type == 'release'
|
||
run: |
|
||
VERSION="${{ steps.version.outputs.version }}"
|
||
DATETIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||
|
||
cat > update-feed.py << 'EOF'
|
||
import sys
|
||
import xml.etree.ElementTree as ET
|
||
|
||
version = sys.argv[1]
|
||
datetime_iso = sys.argv[2]
|
||
|
||
ET.register_namespace('', 'http://www.w3.org/2005/Atom')
|
||
|
||
tree = ET.parse('package-feed.xml')
|
||
root = tree.getroot()
|
||
ns = {'atom': 'http://www.w3.org/2005/Atom'}
|
||
|
||
updated_elem = root.find('atom:updated', ns)
|
||
if updated_elem is not None:
|
||
updated_elem.text = datetime_iso
|
||
|
||
entry_exists = False
|
||
for entry in root.findall('atom:entry', ns):
|
||
title = entry.find('atom:title', ns)
|
||
if title is not None and version in title.text:
|
||
entry_exists = True
|
||
entry_updated = entry.find('atom:updated', ns)
|
||
if entry_updated is not None:
|
||
entry_updated.text = datetime_iso
|
||
break
|
||
|
||
if not entry_exists:
|
||
new_entry = ET.Element('{http://www.w3.org/2005/Atom}entry')
|
||
|
||
title = ET.SubElement(new_entry, '{http://www.w3.org/2005/Atom}title')
|
||
title.text = f"bd.fhir.core version {version}"
|
||
|
||
link = ET.SubElement(new_entry, '{http://www.w3.org/2005/Atom}link')
|
||
link.set('rel', 'alternate')
|
||
link.set('href', f"https://fhir.dghs.gov.bd/core/{version}/")
|
||
|
||
entry_id = ET.SubElement(new_entry, '{http://www.w3.org/2005/Atom}id')
|
||
entry_id.text = f"https://fhir.dghs.gov.bd/core/{version}/"
|
||
|
||
entry_updated = ET.SubElement(new_entry, '{http://www.w3.org/2005/Atom}updated')
|
||
entry_updated.text = datetime_iso
|
||
|
||
summary = ET.SubElement(new_entry, '{http://www.w3.org/2005/Atom}summary')
|
||
summary.text = f"Release {version} of Bangladesh Core FHIR Implementation Guide"
|
||
|
||
insert_pos = 0
|
||
for i, child in enumerate(root):
|
||
if child.tag.endswith('entry'):
|
||
insert_pos = i
|
||
break
|
||
insert_pos = i + 1
|
||
|
||
root.insert(insert_pos, new_entry)
|
||
|
||
tree.write('output/package-feed.xml', encoding='utf-8', xml_declaration=True)
|
||
print(f"✅ Updated package-feed.xml")
|
||
EOF
|
||
|
||
python3 update-feed.py "$VERSION" "$DATETIME"
|
||
|
||
# Also copy the updated package-list.json to output
|
||
cp package-list.json output/package-list.json
|
||
|
||
echo "📋 Updated registry files"
|
||
|
||
- name: Prepare deployment artifact
|
||
run: |
|
||
VERSION="${{ steps.version.outputs.version }}"
|
||
BUILD_TYPE="${{ steps.version.outputs.build_type }}"
|
||
|
||
tar -czf ig-output.tar.gz -C output .
|
||
|
||
echo "version=$VERSION" > deployment.env
|
||
echo "build_type=$BUILD_TYPE" >> deployment.env
|
||
echo "build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> deployment.env
|
||
|
||
# List what's in the output
|
||
echo "📦 Output contents:"
|
||
ls -lh output/ | grep -E "(history\.html|package-list\.json|package-feed\.xml|index\.html)"
|
||
|
||
ls -lh ig-output.tar.gz
|
||
|
||
- name: Upload artifact
|
||
uses: actions/upload-artifact@v3
|
||
with:
|
||
name: ig-output
|
||
path: |
|
||
ig-output.tar.gz
|
||
deployment.env
|
||
package-list.json
|
||
package-feed.xml
|
||
retention-days: 30
|
||
|
||
deploy:
|
||
needs: build-ig
|
||
runs-on: ubuntu-latest
|
||
if: (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||
|
||
steps:
|
||
- name: Download artifact
|
||
uses: actions/download-artifact@v3
|
||
with:
|
||
name: ig-output
|
||
|
||
- name: Load deployment env
|
||
id: deploy_info
|
||
run: |
|
||
source deployment.env
|
||
echo "version=$version" >> $GITHUB_OUTPUT
|
||
echo "build_type=$build_type" >> $GITHUB_OUTPUT
|
||
echo "build_date=$build_date" >> $GITHUB_OUTPUT
|
||
|
||
echo "Deploying version: $version"
|
||
echo "Build type: $build_type"
|
||
|
||
- name: Deploy to server
|
||
uses: appleboy/scp-action@v0.1.7
|
||
with:
|
||
host: ${{ secrets.DEPLOY_HOST }}
|
||
username: ${{ secrets.DEPLOY_USER }}
|
||
password: ${{ secrets.DEPLOY_PASSWORD }}
|
||
port: ${{ secrets.DEPLOY_PORT || 22 }}
|
||
source: "ig-output.tar.gz,deployment.env,package-list.json,package-feed.xml"
|
||
target: "/tmp/fhir-ig-deploy/"
|
||
|
||
- name: Execute deployment on server
|
||
uses: appleboy/ssh-action@v1.0.3
|
||
with:
|
||
host: ${{ secrets.DEPLOY_HOST }}
|
||
username: ${{ secrets.DEPLOY_USER }}
|
||
password: ${{ secrets.DEPLOY_PASSWORD }}
|
||
port: ${{ secrets.DEPLOY_PORT || 22 }}
|
||
script: |
|
||
set -e
|
||
|
||
# Load deployment info
|
||
source /tmp/fhir-ig-deploy/deployment.env
|
||
|
||
echo "=========================================="
|
||
echo "Deploying FHIR IG"
|
||
echo "Version: $version"
|
||
echo "Build Type: $build_type"
|
||
echo "Build Date: $build_date"
|
||
echo "=========================================="
|
||
|
||
# Create version directory structure
|
||
VERSIONS_DIR="/opt/fhir-ig/versions"
|
||
mkdir -p "$VERSIONS_DIR"
|
||
|
||
# Determine target directory
|
||
if [ "$build_type" == "release" ]; then
|
||
TARGET_DIR="$VERSIONS_DIR/$version"
|
||
echo "📦 Deploying release version to: $TARGET_DIR"
|
||
else
|
||
TARGET_DIR="$VERSIONS_DIR/dev"
|
||
echo "🔧 Deploying dev build to: $TARGET_DIR"
|
||
|
||
echo "Cleaning old dev files..."
|
||
rm -rf "$TARGET_DIR"/*
|
||
fi
|
||
|
||
# Create target directory
|
||
mkdir -p "$TARGET_DIR"
|
||
|
||
# Extract IG output
|
||
echo "Extracting IG output..."
|
||
tar -xzf /tmp/fhir-ig-deploy/ig-output.tar.gz -C "$TARGET_DIR"
|
||
|
||
# Verify history.html was deployed
|
||
if [ -f "$TARGET_DIR/history.html" ]; then
|
||
echo "✅ history.html deployed successfully"
|
||
else
|
||
echo "⚠️ WARNING: history.html not found in deployment"
|
||
fi
|
||
|
||
cp /tmp/fhir-ig-deploy/package-list.json "$VERSIONS_DIR/package-list.json"
|
||
|
||
# Copy package-feed.xml to root
|
||
cp /tmp/fhir-ig-deploy/package-feed.xml "$VERSIONS_DIR/package-feed.xml"
|
||
|
||
# Update 'current' symlink for releases
|
||
if [ "$build_type" == "release" ]; then
|
||
echo "Updating 'current' symlink to point to $version"
|
||
rm -f "$VERSIONS_DIR/current"
|
||
ln -sf "$version" "$VERSIONS_DIR/current"
|
||
fi
|
||
|
||
# Ensure nginx container is running with correct config
|
||
cd /opt/fhir-ig
|
||
|
||
# Download deployment files if they don't exist
|
||
if [ ! -f "docker-compose.prod.yml" ]; then
|
||
echo "ERROR: docker-compose.prod.yml not found!"
|
||
echo "Please deploy the updated docker-compose.prod.yml and nginx.conf first"
|
||
exit 1
|
||
fi
|
||
|
||
# Force recreate container to ensure new config/mounts are applied
|
||
# This handles "stuck" states better than a simple restart
|
||
docker compose -f docker-compose.prod.yml up -d --force-recreate fhir-ig
|
||
|
||
# Cleanup
|
||
rm -rf /tmp/fhir-ig-deploy
|
||
|
||
echo "=========================================="
|
||
echo "✅ Deployment completed successfully!"
|
||
echo "Version $version is now available at:"
|
||
if [ "$build_type" == "release" ]; then
|
||
echo " - https://fhir.dghs.gov.bd/core/$version/"
|
||
echo " - https://fhir.dghs.gov.bd/core/$version/history.html"
|
||
echo " - https://fhir.dghs.gov.bd/core/ (current)"
|
||
else
|
||
echo " - https://fhir.dghs.gov.bd/core/dev/"
|
||
fi
|
||
echo "=========================================="
|
||
|
||
# List all versions
|
||
echo "Available versions:"
|
||
ls -lh "$VERSIONS_DIR" | grep -v total
|