#
All checks were successful
FHIR IG CI/CD Pipeline with Version Persistence / build-ig (push) Successful in 6m8s
FHIR IG CI/CD Pipeline with Version Persistence / deploy (push) Successful in 8s

This commit is contained in:
2026-03-07 18:57:46 +06:00
parent dc43651043
commit d82e428e24

View File

@@ -3,7 +3,7 @@ name: FHIR IG CI/CD Pipeline with Version Persistence
on: on:
push: push:
tags: tags:
- 'v*.*.*' # Trigger on version tags like v0.3.0 - 'v*.*.*'
pull_request: pull_request:
branches: [ main ] branches: [ main ]
@@ -24,7 +24,6 @@ jobs:
- name: Extract version from IG - name: Extract version from IG
id: version id: version
run: | run: |
# Extract version from ImplementationGuide resource
VERSION=$(grep -oP '<version value="\K[^"]+' input/bd.fhir.core.xml | head -1) VERSION=$(grep -oP '<version value="\K[^"]+' input/bd.fhir.core.xml | head -1)
if [ -z "$VERSION" ]; then if [ -z "$VERSION" ]; then
@@ -35,12 +34,10 @@ jobs:
echo "Extracted version: $VERSION" echo "Extracted version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
# Determine if this is a release build (git tag) or dev build
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
BUILD_TYPE="release" BUILD_TYPE="release"
TAG_VERSION="${GITHUB_REF#refs/tags/v}" TAG_VERSION="${GITHUB_REF#refs/tags/v}"
# Verify tag matches IG version
if [ "$TAG_VERSION" != "$VERSION" ]; then if [ "$TAG_VERSION" != "$VERSION" ]; then
echo "ERROR: Git tag version ($TAG_VERSION) doesn't match IG version ($VERSION)" echo "ERROR: Git tag version ($TAG_VERSION) doesn't match IG version ($VERSION)"
exit 1 exit 1
@@ -52,18 +49,16 @@ jobs:
echo "build_type=$BUILD_TYPE" >> $GITHUB_OUTPUT echo "build_type=$BUILD_TYPE" >> $GITHUB_OUTPUT
echo "Build type: $BUILD_TYPE" echo "Build type: $BUILD_TYPE"
- name: Pre-build package-list.json and generate history.xml - name: Prepare package-list.json and history.xml for IG Publisher
run: | run: |
VERSION="${{ steps.version.outputs.version }}" VERSION="${{ steps.version.outputs.version }}"
BUILD_TYPE="${{ steps.version.outputs.build_type }}" BUILD_TYPE="${{ steps.version.outputs.build_type }}"
DATE=$(date +%Y-%m-%d) DATE=$(date +%Y-%m-%d)
# Export for Python export VERSION DATE BUILD_TYPE
export VERSION DATE
echo "📋 Preparing package-list.json and history.xml for IG Publisher..." echo "📋 Preparing package-list.json and history.xml for IG Publisher..."
# Check if package-list.json exists in repo
if [ ! -f "package-list.json" ]; then if [ ! -f "package-list.json" ]; then
echo "⚠️ package-list.json not found in repo root" echo "⚠️ package-list.json not found in repo root"
echo "Creating initial package-list.json..." echo "Creating initial package-list.json..."
@@ -86,23 +81,34 @@ jobs:
PKGEOF PKGEOF
fi fi
# For release builds, add the new version entry python3 << 'PYEOF'
if [ "$BUILD_TYPE" == "release" ]; then
echo "📝 Adding version $VERSION to package-list.json..."
python3 << PYEOF
import json import json
import os import os
import sys
version = os.environ.get('VERSION', '') version = os.environ.get('VERSION', '')
date = os.environ.get('DATE', '') date = os.environ.get('DATE', '')
build_type = os.environ.get('BUILD_TYPE', '')
with open('package-list.json', 'r') as f: with open('package-list.json', 'r', encoding='utf-8') as f:
pkg_list = json.load(f) pkg_list = json.load(f)
# Check if this version already exists if 'list' not in pkg_list or not isinstance(pkg_list['list'], list):
version_exists = any(e['version'] == version for e in pkg_list['list']) print("ERROR: package-list.json does not contain a valid 'list' array")
sys.exit(1)
current_entries = [e for e in pkg_list['list'] if e.get('version') == 'current']
if not current_entries:
pkg_list['list'].insert(0, {
"version": "current",
"desc": "Continuous Integration Build (latest in version control)",
"path": "https://fhir.dghs.gov.bd/core/",
"status": "ci-build",
"current": True
})
if build_type == 'release':
version_exists = any(e.get('version') == version for e in pkg_list['list'])
if not version_exists: if not version_exists:
new_entry = { new_entry = {
"version": version, "version": version,
@@ -112,43 +118,44 @@ if not version_exists:
"status": "trial-use", "status": "trial-use",
"sequence": "STU 1" "sequence": "STU 1"
} }
# Insert after 'current' entry
pkg_list['list'].insert(1, new_entry)
with open('package-list.json', 'w') as f: insert_index = 1
json.dump(pkg_list, f, indent=2) for i, entry in enumerate(pkg_list['list']):
if entry.get('version') == 'current':
insert_index = i + 1
break
pkg_list['list'].insert(insert_index, new_entry)
print(f"✅ Added version {version} to package-list.json") print(f"✅ Added version {version} to package-list.json")
else: else:
print(f" Version {version} already exists in package-list.json") print(f" Version {version} already exists in package-list.json")
PYEOF else:
else print(" Dev build - using existing package-list.json without release modification")
echo " Dev build - using existing package-list.json without modifications"
fi with open('package-list.json', 'w', encoding='utf-8') as f:
json.dump(pkg_list, f, indent=2, ensure_ascii=False)
PYEOF
# Validate JSON syntax
echo "🔍 Validating package-list.json..." echo "🔍 Validating package-list.json..."
python3 -m json.tool package-list.json > /dev/null && echo "✅ Valid JSON" || (echo "❌ Invalid JSON!" && exit 1) python3 -m json.tool package-list.json > /dev/null && echo "✅ Valid JSON" || (echo "❌ Invalid JSON!" && exit 1)
# Copy to input/ for IG Publisher
echo "📂 Ensuring package-list.json is in required locations..." echo "📂 Ensuring package-list.json is in required locations..."
mkdir -p input
cp package-list.json input/package-list.json cp package-list.json input/package-list.json
# Generate static history.xml from package-list.json
echo "📝 Generating static history.xml from package-list.json..." echo "📝 Generating static history.xml from package-list.json..."
mkdir -p input/pagecontent mkdir -p input/pagecontent
python3 << 'PYEOF' python3 << 'PYEOF'
import json import json
import os import os
from html import escape
# Ensure directory exists
os.makedirs('input/pagecontent', exist_ok=True) os.makedirs('input/pagecontent', exist_ok=True)
with open('package-list.json', 'r') as f: with open('package-list.json', 'r', encoding='utf-8') as f:
pkg_list = json.load(f) pkg_list = json.load(f)
# Generate static XHTML
xml = '''<?xml version="1.0" encoding="UTF-8"?> xml = '''<?xml version="1.0" encoding="UTF-8"?>
<div xmlns="http://www.w3.org/1999/xhtml"> <div xmlns="http://www.w3.org/1999/xhtml">
<p><b>Version History</b></p> <p><b>Version History</b></p>
@@ -171,16 +178,17 @@ xml = '''<?xml version="1.0" encoding="UTF-8"?>
<tbody> <tbody>
''' '''
# Add each version (skip 'current') published_found = False
for entry in pkg_list['list']: for entry in pkg_list['list']:
if entry['version'] == 'current': if entry.get('version') == 'current':
continue continue
version = entry.get('version', 'Unknown') published_found = True
date = entry.get('date', 'N/A') version = escape(entry.get('version', 'Unknown'))
status = entry.get('status', 'unknown') date = escape(entry.get('date', 'N/A'))
desc = entry.get('desc', '') status = escape(entry.get('status', 'unknown'))
path = entry.get('path', '#') desc = escape(entry.get('desc', ''))
path = escape(entry.get('path', '#'))
xml += f''' <tr> xml += f''' <tr>
<td><a href="{path}">{version}</a></td> <td><a href="{path}">{version}</a></td>
@@ -190,25 +198,37 @@ for entry in pkg_list['list']:
</tr> </tr>
''' '''
if not published_found:
xml += ''' <tr>
<td colspan="4">No published versions available yet.</td>
</tr>
'''
xml += ''' </tbody> xml += ''' </tbody>
</table> </table>
<p><b>Continuous Integration Build</b></p> <p><b>Continuous Integration Build</b></p>
''' '''
# Add CI build info current_entry = None
for entry in pkg_list['list']: for entry in pkg_list['list']:
if entry['version'] == 'current': if entry.get('version') == 'current':
path = entry.get('path', '') current_entry = entry
break
if current_entry:
path = escape(current_entry.get('path', 'https://fhir.dghs.gov.bd/core/'))
xml += f''' <p>The latest development build is available at: <a href="{path}">{path}</a></p> xml += f''' <p>The latest development build is available at: <a href="{path}">{path}</a></p>
<p><i>Note: This is a continuous integration build and may be unstable.</i></p> <p><i>Note: This is a continuous integration build and may be unstable.</i></p>
''' '''
break else:
xml += ''' <p><i>No CI build entry found in package-list.json.</i></p>
'''
xml += '''</div> xml += '''</div>
''' '''
with open('input/pagecontent/history.xml', 'w') as f: with open('input/pagecontent/history.xml', 'w', encoding='utf-8') as f:
f.write(xml) f.write(xml)
print("✅ Generated static history.xml") print("✅ Generated static history.xml")
@@ -216,7 +236,6 @@ print(f" File location: {os.path.abspath('input/pagecontent/history.xml')}")
print(f" File size: {os.path.getsize('input/pagecontent/history.xml')} bytes") print(f" File size: {os.path.getsize('input/pagecontent/history.xml')} bytes")
PYEOF PYEOF
# Verify the file was created
if [ -f "input/pagecontent/history.xml" ]; then if [ -f "input/pagecontent/history.xml" ]; then
echo "✅ Verified: history.xml exists" echo "✅ Verified: history.xml exists"
echo " First 20 lines:" echo " First 20 lines:"
@@ -229,7 +248,23 @@ PYEOF
echo "✅ Pre-build preparation complete:" echo "✅ Pre-build preparation complete:"
echo " - Root: $(pwd)/package-list.json" echo " - Root: $(pwd)/package-list.json"
echo " - Input: $(pwd)/input/package-list.json" echo " - Input: $(pwd)/input/package-list.json"
echo " - History: $(pwd)/input/pagecontent/history.xml (generated)" echo " - History: $(pwd)/input/pagecontent/history.xml"
- name: Emergency Disk Cleanup
run: |
echo "Disk usage before:"
df -h
echo "Clearing tool cache..."
rm -rf /opt/hostedtoolcache/* || true
rm -rf /usr/share/dotnet || true
rm -rf /usr/local/lib/android || true
rm -rf /opt/ghc || true
rm -rf ~/.fhir/packages || true
echo "Disk usage after:"
df -h
- name: Install Docker CLI - name: Install Docker CLI
run: | run: |
@@ -247,32 +282,30 @@ PYEOF
echo "Container ID: $CONTAINER_ID" echo "Container ID: $CONTAINER_ID"
docker cp $(pwd)/. $CONTAINER_ID:/home/publisher/ig/ docker cp "$(pwd)/." "$CONTAINER_ID:/home/publisher/ig/"
docker start -a $CONTAINER_ID docker start -a "$CONTAINER_ID"
EXIT_CODE=$? EXIT_CODE=$?
# Copy outputs
echo "Copying outputs from container..." echo "Copying outputs from container..."
docker cp $CONTAINER_ID:/tmp/build/output ./output || echo "Warning: No output directory" 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/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/input-cache" ./input-cache || echo "No input-cache"
docker cp $CONTAINER_ID:/tmp/build/temp ./temp || echo "No temp directory" docker cp "$CONTAINER_ID:/tmp/build/temp" ./temp || echo "No temp directory"
if [ $EXIT_CODE -ne 0 ]; then if [ $EXIT_CODE -ne 0 ]; then
echo "Build failed, showing logs:" echo "Build failed, showing logs:"
docker logs $CONTAINER_ID docker logs "$CONTAINER_ID"
docker rm $CONTAINER_ID docker rm "$CONTAINER_ID"
exit 1 exit 1
fi fi
docker rm $CONTAINER_ID docker rm "$CONTAINER_ID"
if [ ! -f "output/index.html" ]; then if [ ! -f "output/index.html" ]; then
echo "ERROR: Build failed - no index.html" echo "ERROR: Build failed - no index.html"
exit 1 exit 1
fi fi
# Check if history.html was generated
echo "" echo ""
echo "🔍 Checking for history.html..." echo "🔍 Checking for history.html..."
if [ -f "output/history.html" ]; then if [ -f "output/history.html" ]; then
@@ -280,11 +313,20 @@ PYEOF
echo "📄 history.html size: $(ls -lh output/history.html | awk '{print $5}')" echo "📄 history.html size: $(ls -lh output/history.html | awk '{print $5}')"
else else
echo "⚠️ WARNING: history.html was NOT generated" echo "⚠️ WARNING: history.html was NOT generated"
echo "This might indicate an issue with the template or history.xml" echo "This might indicate an issue with the template or history.xml/package-list.json"
fi fi
echo "✅ Build successful!" echo "✅ Build successful!"
- name: Ensure registry files in output for dev builds
if: steps.version.outputs.build_type == 'dev'
run: |
cp package-list.json output/package-list.json
if [ -f "package-feed.xml" ]; then
cp package-feed.xml output/package-feed.xml
fi
echo "📋 Copied registry files for dev artifact"
- name: Update package-feed.xml for releases - name: Update package-feed.xml for releases
if: steps.version.outputs.build_type == 'release' if: steps.version.outputs.build_type == 'release'
run: | run: |
@@ -311,7 +353,7 @@ if updated_elem is not None:
entry_exists = False entry_exists = False
for entry in root.findall('atom:entry', ns): for entry in root.findall('atom:entry', ns):
title = entry.find('atom:title', ns) title = entry.find('atom:title', ns)
if title is not None and version in title.text: if title is not None and version in (title.text or ''):
entry_exists = True entry_exists = True
entry_updated = entry.find('atom:updated', ns) entry_updated = entry.find('atom:updated', ns)
if entry_updated is not None: if entry_updated is not None:
@@ -347,12 +389,10 @@ if not entry_exists:
root.insert(insert_pos, new_entry) root.insert(insert_pos, new_entry)
tree.write('output/package-feed.xml', encoding='utf-8', xml_declaration=True) tree.write('output/package-feed.xml', encoding='utf-8', xml_declaration=True)
print(f"✅ Updated package-feed.xml") print("✅ Updated package-feed.xml")
EOF EOF
python3 update-feed.py "$VERSION" "$DATETIME" python3 update-feed.py "$VERSION" "$DATETIME"
# Also copy the updated package-list.json to output
cp package-list.json output/package-list.json cp package-list.json output/package-list.json
echo "📋 Updated registry files" echo "📋 Updated registry files"
@@ -387,7 +427,7 @@ EOF
deploy: deploy:
needs: build-ig needs: build-ig
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
steps: steps:
- name: Download artifact - name: Download artifact
@@ -438,15 +478,22 @@ EOF
VERSIONS_DIR="/opt/fhir-ig/versions" VERSIONS_DIR="/opt/fhir-ig/versions"
mkdir -p "$VERSIONS_DIR" mkdir -p "$VERSIONS_DIR"
if [ "$build_type" = "release" ]; then
TARGET_DIR="$VERSIONS_DIR/$version" TARGET_DIR="$VERSIONS_DIR/$version"
echo "📦 Deploying release version to: $TARGET_DIR" echo "📦 Deploying release version to: $TARGET_DIR"
else
TARGET_DIR="$VERSIONS_DIR/dev"
echo "🔧 Deploying dev build to: $TARGET_DIR"
mkdir -p "$TARGET_DIR"
echo "Cleaning old dev files..."
rm -rf "$TARGET_DIR"/*
fi
mkdir -p "$TARGET_DIR" mkdir -p "$TARGET_DIR"
echo "Extracting IG output..." echo "Extracting IG output..."
tar -xzf /tmp/fhir-ig-deploy/ig-output.tar.gz -C "$TARGET_DIR" 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 if [ -f "$TARGET_DIR/history.html" ]; then
echo "✅ history.html deployed successfully" echo "✅ history.html deployed successfully"
else else
@@ -456,28 +503,34 @@ EOF
cp /tmp/fhir-ig-deploy/package-list.json "$VERSIONS_DIR/package-list.json" cp /tmp/fhir-ig-deploy/package-list.json "$VERSIONS_DIR/package-list.json"
cp /tmp/fhir-ig-deploy/package-feed.xml "$VERSIONS_DIR/package-feed.xml" cp /tmp/fhir-ig-deploy/package-feed.xml "$VERSIONS_DIR/package-feed.xml"
if [ "$build_type" = "release" ]; then
echo "Updating 'current' symlink to point to $version" echo "Updating 'current' symlink to point to $version"
rm -f "$VERSIONS_DIR/current" rm -f "$VERSIONS_DIR/current"
ln -sf "$version" "$VERSIONS_DIR/current" ln -sf "$version" "$VERSIONS_DIR/current"
fi
cd /opt/fhir-ig cd /opt/fhir-ig
if [ ! -f "docker-compose.prod.yml" ]; then if [ ! -f "docker-compose.prod.yml" ]; then
echo "ERROR: docker-compose.prod.yml not found!" echo "ERROR: docker-compose.prod.yml not found!"
echo "Please deploy the updated docker-compose.prod.yml and nginx.conf first"
exit 1 exit 1
fi fi
docker compose -f docker-compose.prod.yml restart fhir-ig || \ docker compose -f docker-compose.prod.yml up -d --force-recreate fhir-ig
docker compose -f docker-compose.prod.yml up -d
rm -rf /tmp/fhir-ig-deploy rm -rf /tmp/fhir-ig-deploy
echo "==========================================" echo "=========================================="
echo "✅ Deployment completed successfully!" echo "✅ Deployment completed successfully!"
echo "Version $version is now available at:" 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/"
echo " - https://fhir.dghs.gov.bd/core/$version/history.html" echo " - https://fhir.dghs.gov.bd/core/$version/history.html"
echo " - https://fhir.dghs.gov.bd/core/ (current)" echo " - https://fhir.dghs.gov.bd/core/ (current)"
else
echo " - https://fhir.dghs.gov.bd/core/dev/"
fi
echo "==========================================" echo "=========================================="
echo "Available versions:" echo "Available versions:"