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"]

Reply via email to