Fixes naive vs aware datetime comparison crashes on SQLite (#3562) #1556
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # =============================================================== | |
| # ☁️ ContextForge ▸ Build, Cache & Deploy to IBM Code Engine | |
| # =============================================================== | |
| # | |
| # This workflow: | |
| # - Restores / updates a local **BuildKit layer cache** ❄️ | |
| # - Builds the Docker image from **Containerfile.lite** 🏗️ | |
| # - Pushes the image to **IBM Container Registry (ICR)** 📤 | |
| # - Creates / updates an **IBM Cloud Code Engine** app 🚀 | |
| # | |
| # --------------------------------------------------------------- | |
| # Required repository **secrets** (environment: production) | |
| # --------------------------------------------------------------- | |
| # ┌──────────────────────────────────┬────────────────────────┐ | |
| # │ Secret name │ Purpose │ | |
| # ├──────────────────────────────────┼────────────────────────┤ | |
| # │ IBM_CLOUD_API_KEY │ IBM Cloud IAM API key │ | |
| # │ CF_JWT_SECRET_KEY │ JWT signing key │ | |
| # │ CF_BASIC_AUTH_PASSWORD │ Admin UI password │ | |
| # │ CF_AUTH_ENCRYPTION_SECRET │ Stored-secret cipher │ | |
| # │ CF_PLATFORM_ADMIN_PASSWORD │ Bootstrap admin pass │ | |
| # │ CF_DEFAULT_USER_PASSWORD │ Default user pass │ | |
| # └──────────────────────────────────┴────────────────────────┘ | |
| # | |
| # --------------------------------------------------------------- | |
| # Required repository **variables** (environment: production) | |
| # --------------------------------------------------------------- | |
| # ┌────────────────────────────┬──────────────────────────────┐ | |
| # │ Variable name │ Example value │ | |
| # ├────────────────────────────┼──────────────────────────────┤ | |
| # │ IBM_CLOUD_REGION │ us-south │ | |
| # │ REGISTRY_HOSTNAME │ us.icr.io │ | |
| # │ ICR_NAMESPACE │ myspace │ | |
| # │ APP_NAME │ mcpgateway │ | |
| # │ CODE_ENGINE_PROJECT │ my-ce-project │ | |
| # │ CODE_ENGINE_REGISTRY_SECRET│ my-registry-secret │ | |
| # │ CODE_ENGINE_PORT │ "4444" │ | |
| # └────────────────────────────┴──────────────────────────────┘ | |
| # * Note: CODE_ENGINE_REGISTRY_SECRET is the name of the CE | |
| # registry pull secret, not the secret value itself. | |
| # Triggers: | |
| # - Every push to `main` | |
| # - Syncs the Code Engine secret `mcpgateway-dev` from | |
| # `.env.example` + GitHub Secrets (merge; never logged). | |
| # --------------------------------------------------------------- | |
| name: Deploy to IBM Code Engine | |
| on: | |
| push: | |
| branches: ["main"] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| # ----------------------------------------------------------------- | |
| # Minimal permissions (Principle of Least Privilege) | |
| # ----------------------------------------------------------------- | |
| permissions: | |
| contents: read | |
| # ----------------------------------------------------------------- | |
| # Global environment (secrets & variables) | |
| # ----------------------------------------------------------------- | |
| env: | |
| # Build metadata | |
| GITHUB_SHA: ${{ github.sha }} | |
| CACHE_DIR: /tmp/.buildx-cache # BuildKit layer cache dir | |
| # IBM Cloud region (variable) | |
| IBM_CLOUD_REGION: ${{ vars.IBM_CLOUD_REGION }} | |
| # Registry coordinates (variables) | |
| REGISTRY_HOSTNAME: ${{ vars.REGISTRY_HOSTNAME }} | |
| ICR_NAMESPACE: ${{ vars.ICR_NAMESPACE }} | |
| # Image / app naming (variables) | |
| IMAGE_NAME: ${{ vars.APP_NAME }} | |
| IMAGE_TAG: ${{ github.sha }} | |
| # Code Engine deployment (variables) | |
| CODE_ENGINE_APP_NAME: ${{ vars.APP_NAME }} | |
| CODE_ENGINE_PROJECT: ${{ vars.CODE_ENGINE_PROJECT }} | |
| CODE_ENGINE_REGISTRY_SECRET: ${{ vars.CODE_ENGINE_REGISTRY_SECRET }} | |
| PORT: ${{ vars.CODE_ENGINE_PORT }} | |
| jobs: | |
| build-push-deploy: | |
| if: github.event_name != 'pull_request' || !github.event.pull_request.draft | |
| name: 🚀 Build, Cache, Push & Deploy | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| environment: production | |
| steps: | |
| # ----------------------------------------------------------- | |
| # 0️⃣ Checkout repository | |
| # ----------------------------------------------------------- | |
| - name: ⬇️ Checkout source | |
| uses: actions/checkout@v5 | |
| # ----------------------------------------------------------- | |
| # 1️⃣ Validate required secrets & variables | |
| # ----------------------------------------------------------- | |
| - name: ✅ Validate configuration | |
| env: | |
| CF_IBM_CLOUD_API_KEY: ${{ secrets.IBM_CLOUD_API_KEY }} | |
| CF_JWT_SECRET_KEY: ${{ secrets.CF_JWT_SECRET_KEY }} | |
| CF_BASIC_AUTH_PASSWORD: ${{ secrets.CF_BASIC_AUTH_PASSWORD }} | |
| CF_AUTH_ENCRYPTION_SECRET: ${{ secrets.CF_AUTH_ENCRYPTION_SECRET }} | |
| CF_PLATFORM_ADMIN_PASSWORD: ${{ secrets.CF_PLATFORM_ADMIN_PASSWORD }} | |
| CF_DEFAULT_USER_PASSWORD: ${{ secrets.CF_DEFAULT_USER_PASSWORD }} | |
| run: | | |
| missing=() | |
| placeholder=() | |
| for var in CF_IBM_CLOUD_API_KEY CF_JWT_SECRET_KEY CF_BASIC_AUTH_PASSWORD \ | |
| CF_AUTH_ENCRYPTION_SECRET CF_PLATFORM_ADMIN_PASSWORD \ | |
| CF_DEFAULT_USER_PASSWORD; do | |
| val="${!var}" | |
| if [ -z "$val" ]; then | |
| missing+=("$var") | |
| elif [ "$val" = "-" ] || [ "$val" = "changeme" ] || [ "$val" = "CHANGE_ME" ]; then | |
| placeholder+=("$var") | |
| fi | |
| done | |
| for var in IBM_CLOUD_REGION REGISTRY_HOSTNAME ICR_NAMESPACE \ | |
| IMAGE_NAME CODE_ENGINE_APP_NAME CODE_ENGINE_PROJECT \ | |
| CODE_ENGINE_REGISTRY_SECRET PORT; do | |
| if [ -z "${!var}" ]; then | |
| missing+=("$var") | |
| fi | |
| done | |
| if [ ${#missing[@]} -gt 0 ]; then | |
| echo "::error::Required secrets/variables are empty or unconfigured: ${missing[*]}" | |
| exit 1 | |
| fi | |
| if [ ${#placeholder[@]} -gt 0 ]; then | |
| echo "::error::Secrets contain placeholder values (e.g. '-', 'changeme'): ${placeholder[*]}" | |
| echo "::error::Update these in GitHub → Settings → Environments → production → Secrets" | |
| exit 1 | |
| fi | |
| echo "All required secrets and variables are present." | |
| # ----------------------------------------------------------- | |
| # 2️⃣ Set up Docker Buildx | |
| # ----------------------------------------------------------- | |
| - name: 🛠️ Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3.11.1 | |
| # ----------------------------------------------------------- | |
| # 3️⃣ Restore BuildKit layer cache | |
| # ----------------------------------------------------------- | |
| - name: 🔄 Restore BuildKit cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ env.CACHE_DIR }} | |
| key: ${{ runner.os }}-buildx-${{ github.sha }} | |
| restore-keys: ${{ runner.os }}-buildx- | |
| # ----------------------------------------------------------- | |
| # 4️⃣ Install IBM Cloud CLI + plugins & login | |
| # ----------------------------------------------------------- | |
| - name: 🧰 Install IBM Cloud CLI | |
| uses: IBM/actions-ibmcloud-cli@v1 | |
| with: | |
| api_key: ${{ secrets.IBM_CLOUD_API_KEY }} | |
| region: ${{ vars.IBM_CLOUD_REGION }} | |
| plugins: container-registry, code-engine | |
| # ----------------------------------------------------------- | |
| # 5️⃣ Authenticate to IBM Cloud Code Engine project | |
| # ----------------------------------------------------------- | |
| - name: 🔐 IBM Cloud Code Engine login | |
| run: | | |
| ibmcloud cr region-set "$IBM_CLOUD_REGION" | |
| ibmcloud cr login | |
| ibmcloud ce project select --name "$CODE_ENGINE_PROJECT" | |
| # ----------------------------------------------------------- | |
| # 6️⃣ Build & tag image (cache-aware, amd64 platform) | |
| # ----------------------------------------------------------- | |
| - name: 🏗️ Build Docker image (with cache) | |
| run: | | |
| docker buildx build \ | |
| --platform linux/amd64 \ | |
| --file Containerfile.lite \ | |
| --tag "$REGISTRY_HOSTNAME/$ICR_NAMESPACE/$IMAGE_NAME:$IMAGE_TAG" \ | |
| --cache-from type=local,src=${{ env.CACHE_DIR }} \ | |
| --cache-to type=local,dest=${{ env.CACHE_DIR }},mode=max \ | |
| --load \ | |
| . | |
| # ----------------------------------------------------------- | |
| # 7️⃣ Push image to IBM Container Registry | |
| # ----------------------------------------------------------- | |
| - name: 📤 Push image to ICR | |
| run: | | |
| docker push "$REGISTRY_HOSTNAME/$ICR_NAMESPACE/$IMAGE_NAME:$IMAGE_TAG" | |
| # ----------------------------------------------------------- | |
| # 8️⃣ Build .env from template + GitHub Secrets, sync to CE | |
| # ----------------------------------------------------------- | |
| - name: 🔑 Sync Code Engine secrets | |
| env: | |
| CF_JWT_SECRET_KEY: ${{ secrets.CF_JWT_SECRET_KEY }} | |
| CF_BASIC_AUTH_PASSWORD: ${{ secrets.CF_BASIC_AUTH_PASSWORD }} | |
| CF_AUTH_ENCRYPTION_SECRET: ${{ secrets.CF_AUTH_ENCRYPTION_SECRET }} | |
| CF_PLATFORM_ADMIN_PASSWORD: ${{ secrets.CF_PLATFORM_ADMIN_PASSWORD }} | |
| CF_DEFAULT_USER_PASSWORD: ${{ secrets.CF_DEFAULT_USER_PASSWORD }} | |
| run: | | |
| # Ensure .env.deploy is removed on exit (success or failure) | |
| trap 'rm -f .env.deploy' EXIT | |
| # Build .env from .env.example, replacing secret placeholders. | |
| # Uses Python to avoid shell quoting issues (sed delimiter | |
| # collisions with |, &, \) and grep exit-code edge cases. | |
| # | |
| # IMPORTANT: Output ONLY uncommented KEY=VALUE lines. | |
| # CE's --from-env-file silently skips updates when the file | |
| # contains comments, blank lines, or section headers from | |
| # .env.example. Stripping to clean KEY=VALUE lines fixes this. | |
| python3 -c " | |
| import os, sys | |
| replacements = { | |
| 'JWT_SECRET_KEY': os.environ['CF_JWT_SECRET_KEY'], | |
| 'BASIC_AUTH_PASSWORD': os.environ['CF_BASIC_AUTH_PASSWORD'], | |
| 'AUTH_ENCRYPTION_SECRET': os.environ['CF_AUTH_ENCRYPTION_SECRET'], | |
| 'PLATFORM_ADMIN_PASSWORD': os.environ['CF_PLATFORM_ADMIN_PASSWORD'], | |
| 'DEFAULT_USER_PASSWORD': os.environ['CF_DEFAULT_USER_PASSWORD'], | |
| } | |
| replaced = set() | |
| with open('.env.example') as f: | |
| lines = f.readlines() | |
| with open('.env.deploy', 'w') as out: | |
| for line in lines: | |
| stripped = line.strip() | |
| # Skip comments and blank lines — CE --from-env-file | |
| # needs a clean KEY=VALUE-only file to reliably update. | |
| if not stripped or stripped.startswith('#'): | |
| continue | |
| if '=' not in stripped: | |
| continue | |
| key = stripped.split('=', 1)[0] | |
| if key in replacements: | |
| val = replacements[key] | |
| if '\n' in val or '\r' in val: | |
| print(f'::error::Secret for {key} contains embedded newlines') | |
| raise SystemExit(1) | |
| out.write(f'{key}={val}\n') | |
| replaced.add(key) | |
| else: | |
| out.write(f'{stripped}\n') | |
| missing = set(replacements) - replaced | |
| if missing: | |
| print(f'::error::Keys not found in .env.example: {missing}') | |
| print('Hint: check that .env.example uses KEY=value format (no spaces around =)') | |
| sys.exit(1) | |
| " | |
| echo "✅ Generated .env.deploy ($(wc -l < .env.deploy) KEY=VALUE lines)" | |
| # Update the secret if it exists, otherwise create it. | |
| if ibmcloud ce secret get --name mcpgateway-dev > /dev/null 2>&1; then | |
| ibmcloud ce secret update --name mcpgateway-dev --from-env-file .env.deploy | |
| else | |
| ibmcloud ce secret create --name mcpgateway-dev --format generic --from-env-file .env.deploy | |
| fi | |
| # Belt-and-suspenders: explicitly set each secret with --from-literal | |
| # to guarantee overwrite regardless of --from-env-file parsing quirks. | |
| ibmcloud ce secret update --name mcpgateway-dev \ | |
| --from-literal "JWT_SECRET_KEY=$CF_JWT_SECRET_KEY" \ | |
| --from-literal "BASIC_AUTH_PASSWORD=$CF_BASIC_AUTH_PASSWORD" \ | |
| --from-literal "AUTH_ENCRYPTION_SECRET=$CF_AUTH_ENCRYPTION_SECRET" \ | |
| --from-literal "PLATFORM_ADMIN_PASSWORD=$CF_PLATFORM_ADMIN_PASSWORD" \ | |
| --from-literal "DEFAULT_USER_PASSWORD=$CF_DEFAULT_USER_PASSWORD" | |
| echo "✅ Code Engine secret 'mcpgateway-dev' synced" | |
| # Verify the 5 critical secrets are no longer placeholders | |
| echo "🔍 Verifying secret values were updated..." | |
| for key in JWT_SECRET_KEY BASIC_AUTH_PASSWORD AUTH_ENCRYPTION_SECRET \ | |
| PLATFORM_ADMIN_PASSWORD DEFAULT_USER_PASSWORD; do | |
| val=$(ibmcloud ce secret get --name mcpgateway-dev --decode -o json 2>/dev/null \ | |
| | python3 -c "import json,sys; print(json.load(sys.stdin).get('data',{}).get('$key',''))" 2>/dev/null || echo "") | |
| if [ "$val" = "-" ] || [ -z "$val" ]; then | |
| echo "::error::Secret key $key still has placeholder value after update" | |
| exit 1 | |
| fi | |
| done | |
| echo "✅ All 5 secrets verified (no placeholders)" | |
| # ----------------------------------------------------------- | |
| # 9️⃣ Deploy (create or update) Code Engine application | |
| # ----------------------------------------------------------- | |
| - name: 🚀 Deploy to Code Engine | |
| run: | | |
| if ibmcloud ce application get --name "$CODE_ENGINE_APP_NAME" > /dev/null 2>&1; then | |
| echo "🔁 Updating existing application..." | |
| ibmcloud ce application update \ | |
| --name "$CODE_ENGINE_APP_NAME" \ | |
| --image "$REGISTRY_HOSTNAME/$ICR_NAMESPACE/$IMAGE_NAME:$IMAGE_TAG" \ | |
| --registry-secret "$CODE_ENGINE_REGISTRY_SECRET" \ | |
| --env-from-secret mcpgateway-dev \ | |
| --cpu 1 --memory 2G | |
| else | |
| echo "🆕 Creating new application..." | |
| ibmcloud ce application create \ | |
| --name "$CODE_ENGINE_APP_NAME" \ | |
| --image "$REGISTRY_HOSTNAME/$ICR_NAMESPACE/$IMAGE_NAME:$IMAGE_TAG" \ | |
| --registry-secret "$CODE_ENGINE_REGISTRY_SECRET" \ | |
| --port "$PORT" \ | |
| --env-from-secret mcpgateway-dev \ | |
| --cpu 1 --memory 2G | |
| fi | |
| # ----------------------------------------------------------- | |
| # 🔟 Show deployment status | |
| # ----------------------------------------------------------- | |
| - name: 📈 Display deployment status | |
| run: ibmcloud ce application get --name "$CODE_ENGINE_APP_NAME" |