This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-2-test by this push:
new 899a2c38f59 [v3-2-test] Prek: Prefer gh auth over GitHub token env
(#66692) (#66732)
899a2c38f59 is described below
commit 899a2c38f5936d0b113ff87e0715b5d2ed276978
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue May 12 04:20:30 2026 +0200
[v3-2-test] Prek: Prefer gh auth over GitHub token env (#66692) (#66732)
(cherry picked from commit 502c8b91febcb7aa73ad61253fd0ac04d31b9ebc)
Co-authored-by: Paul <[email protected]>
Co-authored-by: paullee <[email protected]>
Co-authored-by: Jarek Potiuk <[email protected]>
---
scripts/ci/prek/common_prek_utils.py | 42 ++++++++++--
scripts/ci/prek/download_k8s_schemas.py | 19 ++----
scripts/tests/ci/prek/test_common_prek_utils.py | 90 +++++++++++++++++++++++++
3 files changed, 133 insertions(+), 18 deletions(-)
diff --git a/scripts/ci/prek/common_prek_utils.py
b/scripts/ci/prek/common_prek_utils.py
index b5d089f7ab4..f28923b78bc 100644
--- a/scripts/ci/prek/common_prek_utils.py
+++ b/scripts/ci/prek/common_prek_utils.py
@@ -45,7 +45,7 @@ KNOWN_SECOND_LEVEL_PATHS = ["apache", "atlassian", "common",
"cncf", "dbt", "mic
DEFAULT_PYTHON_MAJOR_MINOR_VERSION = "3.10"
-GITHUB_TOKEN: str | None = os.environ.get("GITHUB_TOKEN")
+GITHUB_TOKEN_ENV_VARS = ("GH_TOKEN", "GITHUB_TOKEN")
try:
from rich.console import Console
@@ -589,13 +589,43 @@ def get_remote_for_main() -> str:
return apache_remote or origin_remote or "origin"
-def retrieve_gh_token(*, token: str | None = None, description: str, scopes:
str) -> str:
+def env_without_github_tokens(env: dict[str, str] | None = None) -> dict[str,
str]:
+ cleaned_env = dict(os.environ if env is None else env)
+ for token_env_var in GITHUB_TOKEN_ENV_VARS:
+ cleaned_env.pop(token_env_var, None)
+ return cleaned_env
+
+
+def get_github_token_from_env(env: dict[str, str] | None = None) -> str | None:
+ source_env = os.environ if env is None else env
+ for token_env_var in GITHUB_TOKEN_ENV_VARS:
+ token = source_env.get(token_env_var)
+ if token:
+ return token
+ return None
+
+
+def resolve_github_token(*, token: str | None = None, env: dict[str, str] |
None = None) -> str | None:
+ """Resolve a token while preventing ambient env tokens from shadowing ``gh
auth login``."""
if token:
return token
- if GITHUB_TOKEN:
- return GITHUB_TOKEN
- output = subprocess.check_output(["gh", "auth", "token"])
- token = output.decode().strip()
+ try:
+ result = subprocess.run(
+ ["gh", "auth", "token"],
+ capture_output=True,
+ text=True,
+ check=False,
+ env=env_without_github_tokens(env),
+ )
+ except FileNotFoundError:
+ return get_github_token_from_env(env)
+ if result.returncode == 0 and result.stdout.strip():
+ return result.stdout.strip()
+ return get_github_token_from_env(env)
+
+
+def retrieve_gh_token(*, token: str | None = None, description: str, scopes:
str) -> str:
+ token = resolve_github_token(token=token)
if not token:
if not console:
raise RuntimeError("Please add rich to your script dependencies
and run it again")
diff --git a/scripts/ci/prek/download_k8s_schemas.py
b/scripts/ci/prek/download_k8s_schemas.py
index 2576d5d0754..f5591c5ded8 100755
--- a/scripts/ci/prek/download_k8s_schemas.py
+++ b/scripts/ci/prek/download_k8s_schemas.py
@@ -43,7 +43,12 @@ from tempfile import NamedTemporaryFile
import requests
import yaml
-from common_prek_utils import AIRFLOW_ROOT_PATH, console,
read_allowed_kubernetes_versions
+from common_prek_utils import (
+ AIRFLOW_ROOT_PATH,
+ console,
+ read_allowed_kubernetes_versions,
+ resolve_github_token,
+)
KUBERNETES_VERSIONS = read_allowed_kubernetes_versions()
DEFAULT_KUBERNETES_VERSION = KUBERNETES_VERSIONS[0]
@@ -216,8 +221,6 @@ def download_schemas_for_version(
def main() -> None:
- import os
-
parser = argparse.ArgumentParser(description="Download K8s JSON schemas
for helm chart tests.")
parser.add_argument(
"--output-dir",
@@ -236,15 +239,7 @@ def main() -> None:
output_dir: Path = args.output_dir
versions: list[str] = args.versions if args.versions else
KUBERNETES_VERSIONS
- token = os.environ.get("GITHUB_TOKEN")
- if not token:
- # Try gh CLI
- try:
- result = subprocess.run(["gh", "auth", "token"],
capture_output=True, text=True, check=False)
- if result.returncode == 0 and result.stdout.strip():
- token = result.stdout.strip()
- except FileNotFoundError:
- pass
+ token = resolve_github_token()
if token:
console.print("[green]Using GitHub token for authenticated
requests.[/]")
diff --git a/scripts/tests/ci/prek/test_common_prek_utils.py
b/scripts/tests/ci/prek/test_common_prek_utils.py
index 3ebf6dff400..9c011f8f53c 100644
--- a/scripts/tests/ci/prek/test_common_prek_utils.py
+++ b/scripts/tests/ci/prek/test_common_prek_utils.py
@@ -16,8 +16,10 @@
# under the License.
from __future__ import annotations
+import subprocess
from pathlib import Path
+import ci.prek.common_prek_utils as common_prek_utils
import pytest
from ci.prek.common_prek_utils import (
ConsoleDiff,
@@ -31,6 +33,8 @@ from ci.prek.common_prek_utils import (
read_airflow_version,
read_allowed_kubernetes_versions,
read_default_python_major_minor_version_for_images,
+ resolve_github_token,
+ retrieve_gh_token,
temporary_tsc_project,
)
@@ -38,6 +42,92 @@ PROVIDERS_AMAZON_S3_PATH = "providers/amazon/hooks/s3.py"
AIRFLOW_MODELS_DAG_PATH = "airflow/models/dag.py"
+def _completed_process(returncode: int, stdout: str = "") ->
subprocess.CompletedProcess[str]:
+ return subprocess.CompletedProcess(args=["gh"], returncode=returncode,
stdout=stdout, stderr="")
+
+
+class TestResolveGithubToken:
+ def test_keeps_explicit_token(self, monkeypatch):
+ def fail_if_called(*args, **kwargs):
+ raise AssertionError("subprocess.run should not be called")
+
+ monkeypatch.setattr(common_prek_utils.subprocess, "run",
fail_if_called)
+
+ assert (
+ resolve_github_token(token="explicit-token", env={"GITHUB_TOKEN":
"env-token"})
+ == "explicit-token"
+ )
+
+ def test_prefers_clean_gh_auth_token_over_env_token(self, monkeypatch):
+ calls = []
+
+ def fake_run(*args, **kwargs):
+ calls.append(kwargs)
+ return _completed_process(returncode=0, stdout="stored-gh-token\n")
+
+ monkeypatch.setattr(common_prek_utils.subprocess, "run", fake_run)
+
+ assert (
+ resolve_github_token(env={"GH_TOKEN": "env-gh-token",
"GITHUB_TOKEN": "env-github-token"})
+ == "stored-gh-token"
+ )
+
+ assert calls
+ assert "GH_TOKEN" not in calls[0]["env"]
+ assert "GITHUB_TOKEN" not in calls[0]["env"]
+
+ @pytest.mark.parametrize(
+ ("returncode", "stdout"),
+ [
+ pytest.param(1, "", id="gh-auth-failure"),
+ pytest.param(0, " \n", id="blank-gh-auth-token"),
+ ],
+ )
+ def test_falls_back_to_env_token(self, monkeypatch, returncode, stdout):
+ monkeypatch.setattr(
+ common_prek_utils.subprocess,
+ "run",
+ lambda *args, **kwargs: _completed_process(returncode=returncode,
stdout=stdout),
+ )
+
+ assert (
+ resolve_github_token(env={"GH_TOKEN": "env-gh-token",
"GITHUB_TOKEN": "env-github-token"})
+ == "env-gh-token"
+ )
+
+ def test_falls_back_to_env_token_when_gh_is_missing(self, monkeypatch):
+ def raise_file_not_found(*args, **kwargs):
+ raise FileNotFoundError
+
+ monkeypatch.setattr(common_prek_utils.subprocess, "run",
raise_file_not_found)
+
+ assert resolve_github_token(env={"GITHUB_TOKEN": "env-github-token"})
== "env-github-token"
+
+ def test_retrieve_gh_token_exits_with_help_when_no_token_resolves(self,
monkeypatch):
+ class DummyConsole:
+ def __init__(self):
+ self.messages = []
+
+ def print(self, message):
+ self.messages.append(message)
+
+ dummy_console = DummyConsole()
+ monkeypatch.setattr(common_prek_utils, "console", dummy_console)
+ monkeypatch.delenv("GH_TOKEN", raising=False)
+ monkeypatch.delenv("GITHUB_TOKEN", raising=False)
+ monkeypatch.setattr(
+ common_prek_utils.subprocess,
+ "run",
+ lambda *args, **kwargs: _completed_process(returncode=1),
+ )
+
+ with pytest.raises(SystemExit):
+
retrieve_gh_token(description="airflow-upgrade-important-versions",
scopes="public_repo")
+
+ assert dummy_console.messages
+ assert "GITHUB_TOKEN environment variable is not set" in
dummy_console.messages[0]
+
+
class TestPreProcessMypyFiles:
def test_excludes_conftest(self):
files = ["tests/conftest.py", "tests/test_foo.py"]