Merge pull request #3 from SatyKrish/feature/demo-target-bootstrap-fixes #8
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
| name: deploy | ||
| on: | ||
| pull_request: | ||
| branches: [main] | ||
| push: | ||
| branches: [main] | ||
| jobs: | ||
| validate: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Install Databricks CLI | ||
| run: curl -fsSL https://raw.githubusercontent.com/databricks/setup-cli/main/install.sh | sh | ||
| - name: Validate bundle (demo) | ||
| env: | ||
| DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST }} | ||
| DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }} | ||
| DOCINTEL_WAREHOUSE_ID: ${{ vars.DOCINTEL_WAREHOUSE_ID }} | ||
| run: databricks bundle validate --strict -t demo --var "warehouse_id=$DOCINTEL_WAREHOUSE_ID" | ||
| deploy-demo: | ||
| # CI assumes steady-state: the first-ever bring-up of a workspace must be | ||
| # done locally via `./scripts/bootstrap-demo.sh`, which handles the | ||
| # foundation/consumers staging and waits for Lakebase AVAILABLE. After | ||
| # that initial bring-up, every push to main runs a full bundle deploy | ||
| # against the now-existing resources — no temp-rename trick (DAB would | ||
| # plan to DELETE any resource that disappears from config). | ||
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | ||
| needs: validate | ||
| runs-on: ubuntu-latest | ||
| env: | ||
| DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST }} | ||
| DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }} | ||
| DOCINTEL_CATALOG: ${{ vars.DOCINTEL_CATALOG }} | ||
| DOCINTEL_SCHEMA: ${{ vars.DOCINTEL_SCHEMA }} | ||
| DOCINTEL_WAREHOUSE_ID: ${{ vars.DOCINTEL_WAREHOUSE_ID }} | ||
| PYTHONPATH: ${{ github.workspace }} | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: "3.11" | ||
| - name: Install Databricks CLI | ||
| run: curl -fsSL https://raw.githubusercontent.com/databricks/setup-cli/main/install.sh | sh | ||
| - name: Install Python deps | ||
| run: pip install -r agent/requirements.txt -r evals/requirements.txt | ||
| - name: Deploy bundle (full — consumers already exist in steady-state) | ||
| # Pin warehouse_id so the dashboard + serving env match what | ||
| # wait_for_kpis / log_and_register use. Without --var, the bundle | ||
| # falls back to its `lookup: warehouse: Serverless Starter Warehouse` | ||
| # default and silently picks a different ID. | ||
| run: databricks bundle deploy -t demo --var "warehouse_id=$DOCINTEL_WAREHOUSE_ID" | ||
| - name: Wait for Lakebase instance to be AVAILABLE | ||
| # Lakebase already exists in steady-state but a config change can | ||
| # transition it back through provisioning; the catalog/app bindings | ||
| # need it AVAILABLE before the next bundle run touches them. | ||
| run: | | ||
| python -c " | ||
| import json, os, sys, time, subprocess | ||
| name = os.environ.get('LAKEBASE_NAME') or 'docintel-demo-state-v1' | ||
| deadline = time.time() + 600 | ||
| while True: | ||
| out = subprocess.run(['databricks','api','get','/api/2.0/database/instances','--output','json'], | ||
| capture_output=True, text=True) | ||
| try: | ||
| d = json.loads(out.stdout) | ||
| except Exception: | ||
| d = {} | ||
| state = next((i.get('state') for i in d.get('database_instances',[]) if i.get('name')==name), 'UNKNOWN') | ||
| print(f'lakebase state: {state}') | ||
| if state == 'AVAILABLE': | ||
| sys.exit(0) | ||
| if time.time() >= deadline: | ||
| sys.exit(f'Lakebase {name} did not reach AVAILABLE within 600s (state={state})') | ||
| time.sleep(15) | ||
| " | ||
| env: | ||
| LAKEBASE_NAME: ${{ vars.DOCINTEL_LAKEBASE_NAME || 'docintel-demo-state-v1' }} | ||
| - name: Refresh data — upload samples, run pipeline, register new model version | ||
| run: | | ||
| for f in samples/*_10K_*.pdf; do | ||
| databricks fs cp "$f" \ | ||
| "dbfs:/Volumes/${DOCINTEL_CATALOG}/${DOCINTEL_SCHEMA}/raw_filings/" \ | ||
| --overwrite | ||
| done | ||
| databricks bundle run -t demo --var "warehouse_id=$DOCINTEL_WAREHOUSE_ID" doc_intel_pipeline | ||
| python scripts/wait_for_kpis.py --min-rows 3 --timeout 900 | ||
| # --serving-endpoint repoints the existing endpoint to the new | ||
| # model version in-place (steady-state idempotent operation). | ||
| python agent/log_and_register.py --target demo --serving-endpoint analyst-agent-demo | ||
| - name: Apply UC grants (catalog + schema; not DAB-supported) | ||
| # UC requires the full chain: USE_CATALOG → USE_SCHEMA → SELECT/EXECUTE. | ||
| # Defaults to "account users" so CI does not silently skip when the | ||
| # GitHub variable is unset. | ||
| env: | ||
| ANALYST_GROUP: ${{ vars.DOCINTEL_ANALYST_GROUP || 'account users' }} | ||
| run: | | ||
| databricks api patch \ | ||
| "/api/2.1/unity-catalog/permissions/CATALOG/${DOCINTEL_CATALOG}" \ | ||
| --json "{\"changes\":[{\"principal\":\"${ANALYST_GROUP}\",\"add\":[\"USE_CATALOG\"]}]}" || true | ||
| databricks api patch \ | ||
| "/api/2.1/unity-catalog/permissions/SCHEMA/${DOCINTEL_CATALOG}.${DOCINTEL_SCHEMA}" \ | ||
| --json "{\"changes\":[{\"principal\":\"${ANALYST_GROUP}\",\"add\":[\"USE_SCHEMA\",\"SELECT\",\"EXECUTE\"]}]}" || true | ||
| - name: Apply app config + restart | ||
| # Databricks Apps deploy docs: | ||
| # https://docs.databricks.com/aws/en/dev-tools/databricks-apps/deploy | ||
| # `bundle deploy` alone uploads code but doesn't apply config/restart. | ||
| run: databricks bundle run -t demo --var "warehouse_id=$DOCINTEL_WAREHOUSE_ID" analyst_app | ||
| - name: Verify OBO scopes survived deploy | ||
| # `bundle run` may wipe user_api_scopes (documented destructive-update | ||
| # behavior). Fail loudly so we re-apply. Skipped when user_api_scopes | ||
| # are not declared (workspace feature off). | ||
| run: | | ||
| if grep -q '^ user_api_scopes:' resources/consumers/analyst.app.yml; then | ||
| databricks apps get doc-intel-analyst-demo --output json > /tmp/app.json | ||
| python -c " | ||
| import json | ||
| app = json.load(open('/tmp/app.json')) | ||
| scopes = set(app.get('user_api_scopes') or []) | ||
| required = {'serving.serving-endpoints', 'sql'} | ||
| missing = required - scopes | ||
| assert not missing, f'OBO scopes missing: {sorted(missing)} (got {sorted(scopes)})' | ||
| " | ||
| else | ||
| echo "user_api_scopes not declared (workspace feature off); skipping OBO scope check" | ||
| fi | ||
| - name: CLEARS evaluation gate | ||
| run: python evals/clears_eval.py --endpoint analyst-agent-demo --dataset evals/dataset.jsonl | ||