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 '> $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