This is an automated email from the ASF dual-hosted git repository.
shahar1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 621192d76a9 Add upgrade-fab-provider skill and FAB contributing doc
(#69729)
621192d76a9 is described below
commit 621192d76a9fec1f9f105d0597f9ddcd955c35b7
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sat Jul 11 15:00:24 2026 +0200
Add upgrade-fab-provider skill and FAB contributing doc (#69729)
Upgrading the pinned Flask-AppBuilder dependency is deceptively involved:
the FAB provider vendors and subclasses large parts of FAB's security
manager, so a bump must be reconciled against that vendored code, the
alignment-test tripwire, and the generated provider README. Capturing the
verified procedure as a skill — and a matching
providers/fab/CONTRIBUTING.rst
that points contributors at it and records past bumps — lets future FAB
upgrades follow the same checklist instead of rediscovering it each time.
The skill lives vendor-neutrally in .agents/skills with .claude and .github
relays, matching the repository's existing skill layout.
---
.agents/skills/upgrade-fab-provider/SKILL.md | 270 +++++++++++++++++++++++++++
.claude/skills/upgrade-fab-provider | 1 +
.github/skills/upgrade-fab-provider | 1 +
.gitignore | 1 +
providers/fab/CONTRIBUTING.rst | 56 ++++++
5 files changed, 329 insertions(+)
diff --git a/.agents/skills/upgrade-fab-provider/SKILL.md
b/.agents/skills/upgrade-fab-provider/SKILL.md
new file mode 100644
index 00000000000..1b08f8aa22a
--- /dev/null
+++ b/.agents/skills/upgrade-fab-provider/SKILL.md
@@ -0,0 +1,270 @@
+---
+name: upgrade-fab-provider
+description: >
+ Upgrade the pinned Flask-AppBuilder (FAB) dependency in the Apache Airflow
+ FAB provider (`providers/fab/`). Bumps the exact `flask-appbuilder==` pin and
+ its mirror constant, regenerates `uv.lock`, drives the
`test_fab_alignment.py`
+ drift tripwire, reviews the vendored security-manager `override.py` against
the
+ new upstream FAB, and conditionally re-vendors static assets / DB migrations.
+ Use when asked to "upgrade FAB", "bump flask-appbuilder", or move the FAB
+ provider to a newer Flask-AppBuilder release.
+license: Apache-2.0
+---
+
+<!-- SPDX-License-Identifier: Apache-2.0
+ https://www.apache.org/licenses/LICENSE-2.0 -->
+
+# upgrade-fab-provider
+
+Airflow's FAB provider is **tightly coupled** to a specific Flask-AppBuilder
+release because it vendors-in and subclasses large parts of FAB's security
+manager. A version bump is therefore never "just change the pin" — it must be
+reconciled against the vendored code, and that reconciliation is enforced by a
+pytest **alignment test**, not a prek hook.
+
+The canonical reference for a real bump is PR **#66841** ("Bump
flask-appbuilder
+to 5.2.1 and mirror new auth event hooks") — commit `c72b6613fd`. Read its diff
+first when in doubt: `git show c72b6613fd`.
+
+## The coupling — why this is not a one-line change
+
+- `providers/fab/pyproject.toml:75-80` explains it: Airflow vendored FAB's
+ security-manager code into
+
`providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py`
+ (~2700 lines) as `FabAirflowSecurityManagerOverride`. Every bump must review
+ that class against upstream FAB for new / changed / removed methods.
+- `test_fab_alignment.py` mechanically detects drift between the **installed**
+ FAB package and the vendored override, and **fails CI** until the developer
+ reconciles it.
+
+## Inputs
+
+- **Target version** — the FAB version to move to. If not given, use the latest
+ release on PyPI (`https://pypi.org/pypi/flask-appbuilder/json` →
`info.version`).
+ Confirm the target with the user if it is a **major or minor** bump (higher
+ reconciliation risk); a **patch** bump can proceed.
+
+## The files a bump touches
+
+**Always:**
+
+1. `providers/fab/pyproject.toml` — line ~80, the `flask-appbuilder==X.Y.Z` pin
+ (the **only** real dependency pin in the repo).
+2.
`providers/fab/tests/unit/fab/auth_manager/security_manager/test_fab_alignment.py`
+ — `EXPECTED_FAB_VERSION = "X.Y.Z"` (line ~43). Must move in lockstep with
the pin.
+3. `providers/fab/docs/index.rst` — the dependency table row
+ ``` ``flask-appbuilder`` ``==X.Y.Z`` ``` (line ~114).
+4. `providers/fab/README.rst` — the Requirements table row (line ~60). **Do
+ not hand-edit** — it is auto-generated. Regenerate it from the bumped
+ `pyproject.toml` with the `sync-provider-readme` prek hook (Step 8); the
hook
+ re-renders the table whenever `pyproject.toml` changes. (Pre-existing bumps
+ that predate this hook left it to release-time regeneration; today the hook
is
+ per-commit, so CI flags the drift — run it.)
+5. `uv.lock` — regenerated (see Step 4 for the pinned-uv caveat).
+
+**Conditionally:**
+
+6.
`providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py`
+ — transplant any relevant upstream FAB changes (new auth hooks, changed
+ signatures, ported fixes). The reference bump added +37 lines here.
**Note:**
+ a green alignment test does not prove the transplant is unnecessary — a
+ security fix may live inside a method Airflow vendors (see the 5.2.2 worked
+ example below).
+7. `providers/fab/src/airflow/providers/fab/www/**` +
`providers/fab/www-hash.txt`
+ — only if the new FAB ships changed static assets / templates that are
+ re-vendored (see Step 6).
+8. `providers/fab/src/airflow/providers/fab/migrations/versions/**` — only if
the
+ new FAB version ships DB migrations (see Step 7).
+
+**Never:** no newsfragment, and do **not** hand-edit
`providers/fab/docs/changelog.rst`
+— providers are released from `main` and the release manager regenerates the
+changelog from `git log` (per `providers/AGENTS.md`). The commit subject is the
+changelog entry.
+
+## Procedure
+
+### Step 1 — Determine the target version and current state
+
+- Read the current pin: `grep flask-appbuilder== providers/fab/pyproject.toml`.
+- Resolve the target (PyPI latest, or the user's requested version).
+- Classify the jump: patch / minor / major. For minor/major, skim the FAB
+ release notes (`https://github.com/dpgaspar/Flask-AppBuilder/releases`) for
+ security-manager / model / template changes before editing.
+
+### Step 2 — Bump the three source pins
+
+Edit in lockstep:
+
+- `providers/fab/pyproject.toml` — the `flask-appbuilder==X.Y.Z` line (keep the
+ trailing `# Whenever updating the version, run test_fab_alignment.py to
verify.`
+ comment).
+- `test_fab_alignment.py` — `EXPECTED_FAB_VERSION = "X.Y.Z"`. It is the
tripwire;
+ editing it now is fine — you will keep re-running the test until it and the
+ other three tests pass.
+- `providers/fab/docs/index.rst` — the dependency-table `==X.Y.Z` row.
+
+### Step 3 — Install the new FAB into the provider venv
+
+`uv --project providers/fab sync` installs the new pin. Confirm the installed
+version:
+
+```bash
+uv run --project providers/fab python -c \
+ "import importlib.metadata as m; print(m.version('flask-appbuilder'))"
+```
+
+### Step 4 — Regenerate uv.lock (pinned uv!)
+
+The lock **must** be regenerated with the repo's pinned uv version, or it
drifts
+hundreds of unrelated marker lines:
+
+```bash
+AIRFLOW_UV_VERSION=$(grep -oE 'AIRFLOW_UV_VERSION=[0-9.]+' Dockerfile.ci |
head -1 | cut -d= -f2)
+uvx --from uv==$AIRFLOW_UV_VERSION uv lock
+```
+
+If a conflict is irrecoverable, delete `uv.lock` and re-run `uv lock` with the
+pinned version. Confirm the diff is limited to the FAB bump, not a wholesale
+marker rewrite.
+
+### Step 5 — Run the alignment test and reconcile override.py
+
+```bash
+uv run --project providers/fab pytest \
+
providers/fab/tests/unit/fab/auth_manager/security_manager/test_fab_alignment.py
-xvs
+```
+
+Under the host sandbox the test needs a writable `AIRFLOW_HOME` and the
+rerun-failures socket disabled — if it errors on a socket `bind` or on
+`~/airflow`, run it as:
+
+```bash
+AIRFLOW_HOME="$TMPDIR/fab_home" uv run --project providers/fab pytest \
+ providers/fab/tests/.../test_fab_alignment.py -q -p no:rerunfailures
+```
+
+The four tests and how to fix each:
+
+1. **`test_fab_version_matches_expected`** — trips on the version mismatch. It
+ passes once `EXPECTED_FAB_VERSION` == installed version, but only after you
+ have done the review below.
+2. **`test_no_unaudited_fab_methods`** — a new FAB public method exists that is
+ neither implemented in `override.py` nor listed in `AUDITED_EXCLUSIONS`.
+ Fix: either implement/override it in `override.py`, or add it to
+ `AUDITED_EXCLUSIONS` **with a justification comment**.
+3. **`test_no_stale_exclusions`** — `AUDITED_EXCLUSIONS` lists a method the new
+ FAB no longer has. Fix: remove that entry.
+4. **`test_shared_method_signatures_compatible`** — FAB changed a method
+ signature (new required param). Fix: update the `override.py` signature, or
+ add to `KNOWN_SIGNATURE_DEVIATIONS` if the divergence is intentional.
+
+**The manual review that the test cannot fully automate:** diff the vendored
+`override.py` against the new FAB's `flask_appbuilder/security/sqla/manager.py`
+and `BaseSecurityManager` and **transplant behavioural changes** (bug fixes,
new
+auth event hooks), not just signatures — the test only checks method *presence*
+and *required params*. Locate the installed source:
+
+```bash
+uv run --project providers/fab python -c \
+ "import flask_appbuilder.security.sqla.manager as m; print(m.__file__)"
+```
+
+### Step 6 — Re-vendor static assets / templates (only if changed)
+
+If the new FAB changed frontend assets that Airflow vendors under
+`providers/fab/src/airflow/providers/fab/www/` (templates in
+`templates/appbuilder/`, static JS/CSS), re-vendor them, then regenerate the
+fingerprint:
+
+```bash
+prek run compile-fab-assets --all-files
+```
+
+This runs `scripts/ci/prek/compile_provider_assets.py fab` (pnpm build over
+`www/`) and rewrites `providers/fab/www-hash.txt`. A patch bump usually does
+**not** touch assets — skip this step unless FAB's templates/static changed.
+Commit the regenerated `www-hash.txt` if it changed.
+
+### Step 7 — DB migrations (only if FAB ships them)
+
+If the new FAB adds/changes security-model tables, add the corresponding
+migration under `providers/fab/src/airflow/providers/fab/migrations/versions/`
+and run:
+
+```bash
+prek run update-migration-references-fab check-revision-heads-map-fab
--all-files
+```
+
+Patch bumps normally have no migrations — skip unless the release notes mention
+schema changes.
+
+### Step 8 — Static checks + tests
+
+```bash
+prek run --from-ref main --stage pre-commit
+uv run --project providers/fab pytest
providers/fab/tests/unit/fab/auth_manager -xvs
+```
+
+The pre-commit stage runs `sync-provider-readme` (regenerating `README.rst`)
and
+other FAB hooks. Re-run the alignment test until all four tests pass. The full
+provider suite is `breeze testing providers-tests --test-type
"Providers[fab]"`.
+
+### Step 9 — Self-review and commit
+
+- `git diff main...HEAD` — verify only the intended files changed, and
`uv.lock`
+ is a clean FAB-scoped diff.
+- Commit subject in imperative mood, plain prose, **no** Conventional-Commits
+ prefix, e.g. `Bump flask-appbuilder to X.Y.Z in FAB provider`. Body explains
+ *why* (what upstream changes were mirrored), not what.
+- No newsfragment, no changelog edit (provider release manager regenerates from
+ git log).
+- Prepend your PR to the history list in `providers/fab/CONTRIBUTING.rst` so
the
+ FAB-upgrade record stays current.
+- Push to `origin` and open the PR per the repo's PR conventions.
+
+## Gotchas
+
+- **`EXPECTED_FAB_VERSION` is a second pin.** Forgetting it makes
+ `test_fab_alignment.py` fail even when everything else is correct.
+- **`uv.lock` marker drift.** Always use the pinned uv (Step 4) — a bare
+ `uv lock` rewrites hundreds of environment-marker lines and buries the real
diff.
+- **The alignment test uses AST, not import**, to read FAB's `SecurityManager`
+ (to avoid SQLAlchemy model-registry collisions with Airflow's vendored
models).
+ A green test proves *structural* alignment; it does **not** prove behavioural
+ parity — Step 5's manual transplant review is still required.
+- **`docs/index.rst` dependency table** may look auto-generated but the
reference
+ PR edited it by hand; if a docs-regen prek hook rewrites it, let the hook
win.
+- **`README.rst` is generated, but the sync hook is per-commit.** Don't
hand-edit
+ it; run `prek run sync-provider-readme` (or the full pre-commit stage) after
+ bumping `pyproject.toml` — CI fails on the drift otherwise.
+- **Don't touch `providers/fab/docs/upgrading.rst`** — that is end-user
guidance
+ for upgrading the *provider package* in a deployment, not the developer bump
+ workflow.
+
+## Worked example — 5.2.1 to 5.2.2 (patch)
+
+A patch bump that needed **no `override.py` change**, but only after a real
+behavioural review — the green alignment test alone was not sufficient
evidence:
+
+- FAB 5.2.2 shipped three security-manager fixes: LDAP search-filter escaping,
+ OAuth email-allowlist regex anchoring (`email + "$"`), and API-login provider
+ validation.
+- All three touch methods Airflow *vendors* (`_search_ldap`, `auth_user_ldap`,
+ `auth_user_oauth`) — so the alignment test passing did **not** mean "nothing
to
+ do". Each had to be checked by hand:
+ - **LDAP escaping** — Airflow's vendored `_search_ldap` **already** escapes
via
+ `ldap.filter.escape_filter_chars` (and adds filter-parenthesis
validation); it
+ was ahead of FAB. No transplant.
+ - **OAuth allowlist anchoring** — the match lives in FAB's `AuthOAuthView`
+ (`views.py`), and Airflow's `CustomAuthOAuthView.oauth_authorized`
delegates
+ via `super().oauth_authorized()`, so the fix is inherited from the
installed
+ FAB 5.2.2. No transplant.
+ - **API-login validation / Azure-JWT warning / uuid4** — in FAB core Airflow
+ doesn't vendor. Inherited.
+- Net change set: `pyproject.toml`, `test_fab_alignment.py`, `docs/index.rst`,
+ `README.rst` (via hook), `uv.lock`. Commit: `Bump flask-appbuilder to 5.2.2
in
+ FAB provider`.
+
+The lesson the skill encodes: **for every security/behavioural fix in the FAB
+release notes, locate the method and check whether Airflow vendors it** — the
+alignment test guards structure, you guard behaviour.
diff --git a/.claude/skills/upgrade-fab-provider
b/.claude/skills/upgrade-fab-provider
new file mode 120000
index 00000000000..d7c96f92a70
--- /dev/null
+++ b/.claude/skills/upgrade-fab-provider
@@ -0,0 +1 @@
+../../.agents/skills/upgrade-fab-provider
\ No newline at end of file
diff --git a/.github/skills/upgrade-fab-provider
b/.github/skills/upgrade-fab-provider
new file mode 120000
index 00000000000..d7c96f92a70
--- /dev/null
+++ b/.github/skills/upgrade-fab-provider
@@ -0,0 +1 @@
+../../.agents/skills/upgrade-fab-provider
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 10880ec8371..bc988309f83 100644
--- a/.gitignore
+++ b/.gitignore
@@ -139,6 +139,7 @@ ENV/
!.claude/skills/aip-user-stories
!.claude/skills/airflow-translations
!.claude/skills/prepare-providers-documentation
+!.claude/skills/upgrade-fab-provider
!.claude/skills/magpie-setup
# Kiro
diff --git a/providers/fab/CONTRIBUTING.rst b/providers/fab/CONTRIBUTING.rst
new file mode 100644
index 00000000000..091338ce34b
--- /dev/null
+++ b/providers/fab/CONTRIBUTING.rst
@@ -0,0 +1,56 @@
+ .. Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ .. http://www.apache.org/licenses/LICENSE-2.0
+
+ .. Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+Contributing to the FAB provider
+================================
+
+This page collects developer notes for the ``apache-airflow-providers-fab``
+distribution. It is intentionally kept out of the published provider
+documentation under ``docs/`` — it targets contributors editing the provider,
+not deployment users.
+
+Upgrading Flask-AppBuilder
+--------------------------
+
+The FAB provider is **tightly coupled** to a specific Flask-AppBuilder (FAB)
+release: it vendors and subclasses large parts of FAB's security manager in
+``src/airflow/providers/fab/auth_manager/security_manager/override.py`` (and
+vendors FAB's web assets/templates under ``src/airflow/providers/fab/www/``).
+Bumping the pinned ``flask-appbuilder`` version is therefore never just a pin
+change — the vendored code must be reconciled against the new release, which
+``tests/unit/fab/auth_manager/security_manager/test_fab_alignment.py`` enforces
+in CI.
+
+Use the ``upgrade-fab-provider`` agent skill
+(``.agents/skills/upgrade-fab-provider/``) to perform a bump end-to-end. It
+drives the pin change, the ``uv.lock`` regeneration, the alignment-test
+tripwire, the manual review of the vendored ``override.py`` against upstream
+FAB, and the generated-README sync — and documents the gotchas that a green
+alignment test alone does not catch (a security fix can live inside a method
+Airflow vendors).
+
+Past FAB version bumps, for reference (newest first):
+
+* ``5.2.1`` -> ``5.2.2``: https://github.com/apache/airflow/pull/69730
+* ``5.2.0`` -> ``5.2.1``: https://github.com/apache/airflow/pull/66841
+* ``5.0.1`` -> ``5.2.0``: https://github.com/apache/airflow/pull/62924
+* ``5.0.0`` -> ``5.0.1``: https://github.com/apache/airflow/pull/57170
+* ``4.6.3`` -> ``5.0.0``: https://github.com/apache/airflow/pull/50960
+* ``4.5.3`` -> ``4.6.3``: https://github.com/apache/airflow/pull/50513
+
+When you complete a bump, prepend your PR to this list so the history stays
+current.