Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 3 additions & 11 deletions dev/breeze/src/airflow_breeze/commands/ci_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
fix_ownership_using_docker,
perform_environment_checks,
)
from airflow_breeze.utils.github import retrieve_github_token
from airflow_breeze.utils.path_utils import AIRFLOW_HOME_PATH, AIRFLOW_ROOT_PATH
from airflow_breeze.utils.run_utils import run_command

Expand Down Expand Up @@ -778,16 +779,7 @@ def upgrade(

console_print("[info]Running upgrade of important CI environment.[/]")

# Resolve GitHub token: prefer --github-token / GITHUB_TOKEN env var, fall back to gh CLI
if not github_token:
gh_token_result = run_command(
["gh", "auth", "token"],
capture_output=True,
text=True,
check=False,
)
if gh_token_result.returncode == 0 and gh_token_result.stdout.strip():
github_token = gh_token_result.stdout.strip()
github_token = retrieve_github_token(github_token)

# Create a copy of the environment to pass to commands
command_env = os.environ.copy()
Expand All @@ -797,7 +789,7 @@ def upgrade(
console_print("[success]GitHub token set in environment.[/]")
else:
console_print(
"[warning]Could not retrieve GitHub token from --github-token or gh CLI. "
"[warning]Could not retrieve GitHub token from --github-token, gh CLI, or token env. "
"Commands may fail if they require authentication.[/]"
)

Expand Down
15 changes: 2 additions & 13 deletions dev/breeze/src/airflow_breeze/commands/issues_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from airflow_breeze.utils.click_utils import BreezeGroup
from airflow_breeze.utils.confirm import Answer, user_confirm
from airflow_breeze.utils.console import console_print
from airflow_breeze.utils.run_utils import run_command
from airflow_breeze.utils.github import retrieve_github_token
from airflow_breeze.utils.shared_options import get_dry_run


Expand All @@ -42,18 +42,7 @@ def issues_group():

def _resolve_github_token(github_token: str | None) -> str | None:
"""Resolve GitHub token from option, environment, or gh CLI."""
if github_token:
return github_token
gh_token_result = run_command(
["gh", "auth", "token"],
capture_output=True,
text=True,
check=False,
dry_run_override=False,
)
if gh_token_result.returncode == 0:
return gh_token_result.stdout.strip()
return None
return retrieve_github_token(github_token)


def _get_collaborator_logins(repo) -> set[str]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
fix_ownership_using_docker,
perform_environment_checks,
)
from airflow_breeze.utils.github import retrieve_github_token
from airflow_breeze.utils.helm_chart_utils import chart_version
from airflow_breeze.utils.packages import (
PackageSuspendedException,
Expand Down Expand Up @@ -2675,16 +2676,7 @@ class ProviderPRInfo(NamedTuple):
all_prs.update(prs)
provider_prs[provider_id] = filtered_prs
all_retrieved_prs.update(provider_prs[provider_id])
if not github_token:
# Get GitHub token from gh CLI and set it in environment copy
gh_token_result = run_command(
["gh", "auth", "token"],
capture_output=True,
text=True,
check=False,
)
if gh_token_result.returncode == 0:
github_token = gh_token_result.stdout.strip()
github_token = retrieve_github_token(github_token)
g = Github(github_token)
repo = g.get_repo("apache/airflow")
pull_requests: dict[int, PullRequest.PullRequest | Issue.Issue] = {}
Expand Down Expand Up @@ -3062,17 +3054,7 @@ def generate_issue_content_core(

def _get_github_token(github_token: str) -> str:
"""Return github_token as-is, or fall back to ``gh auth token``."""
if github_token:
return github_token
result = run_command(
["gh", "auth", "token"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return result.stdout.strip()
return github_token
return retrieve_github_token(github_token) or github_token
Comment thread
leeyspaul marked this conversation as resolved.
Outdated


def _get_airflowctl_prs(
Expand Down
15 changes: 5 additions & 10 deletions dev/breeze/src/airflow_breeze/commands/workflow_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from airflow_breeze.utils.console import console_print
from airflow_breeze.utils.custom_param_types import BetterChoice
from airflow_breeze.utils.gh_workflow_utils import trigger_workflow_and_monitor
from airflow_breeze.utils.run_utils import run_command
from airflow_breeze.utils.github import run_gh_command

WORKFLOW_NAME_MAPS = {
"publish-docs": "publish-docs-to-s3.yml",
Expand Down Expand Up @@ -127,24 +127,19 @@ def workflow_run_publish(
)
sys.exit(1)
if os.environ.get("GITHUB_TOKEN", ""):
console_print("\n[warning]Your authentication will use GITHUB_TOKEN environment variable.")
console_print("\n[warning]GITHUB_TOKEN environment variable is set.")
Comment thread
leeyspaul marked this conversation as resolved.
Outdated
console_print(
"\nThis might not be what you want unless your token has "
"sufficient permissions to trigger workflows."
)
console_print(
"If you remove GITHUB_TOKEN, workflow_run will use the authentication you already "
"set-up with `gh auth login`.\n"
"\nBreeze will first try your `gh auth login` credentials and use GITHUB_TOKEN only as "
"a fallback. The fallback token needs sufficient permissions to trigger workflows."
)
console_print(
f"[blue]Validating ref: {ref}[/blue]",
)

if not skip_tag_validation:
tag_result = run_command(
tag_result = run_gh_command(
["gh", "api", f"repos/apache/airflow/git/refs/tags/{ref}"],
capture_output=True,
check=False,
)

stdout = tag_result.stdout.decode("utf-8")
Expand Down
8 changes: 4 additions & 4 deletions dev/breeze/src/airflow_breeze/utils/gh_workflow_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from airflow_breeze.global_constants import MIN_GH_VERSION
from airflow_breeze.utils.console import console_print
from airflow_breeze.utils.run_utils import run_command
from airflow_breeze.utils.github import run_gh_command


def tigger_workflow(workflow_name: str, repo: str, branch: str = "main", **kwargs):
Expand All @@ -50,7 +50,7 @@ def tigger_workflow(workflow_name: str, repo: str, branch: str = "main", **kwarg
command.extend(["-f", f"{key}={value}"])

console_print(f"[blue]Running command: {' '.join(command)}[/blue]")
result = run_command(command, capture_output=True, check=False)
result = run_gh_command(command, capture_output=True)
Comment thread
leeyspaul marked this conversation as resolved.

if result.returncode != 0:
console_print(f"[red]Error running workflow: {result.stderr}[/red]")
Expand Down Expand Up @@ -109,7 +109,7 @@ def get_workflow_run_id(workflow_name: str, repo: str) -> int:
"databaseId",
]

result = run_command(command, capture_output=True, check=False)
result = run_gh_command(command, capture_output=True)
if result.returncode != 0:
console_print(f"[red]Error fetching workflow run ID: {result.stderr}[/red]")
sys.exit(1)
Expand Down Expand Up @@ -139,7 +139,7 @@ def get_workflow_run_info(run_id: str, repo: str, fields: str) -> dict:
make_sure_gh_is_installed()
command = ["gh", "run", "view", run_id, "--json", fields, "--repo", repo]

result = run_command(command, capture_output=True, check=False)
result = run_gh_command(command, capture_output=True)
if result.returncode != 0:
console_print(f"[red]Error fetching workflow run status: {result.stderr}[/red]")
sys.exit(1)
Expand Down
77 changes: 77 additions & 0 deletions dev/breeze/src/airflow_breeze/utils/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@

import os
import re
import subprocess
import sys
import tempfile
import zipfile
from collections.abc import Mapping, Sequence
from datetime import datetime, timezone
from pathlib import Path
from typing import TYPE_CHECKING, Any
Expand All @@ -35,6 +37,81 @@
if TYPE_CHECKING:
from requests import Response

GITHUB_TOKEN_ENV_VARS = ("GH_TOKEN", "GITHUB_TOKEN")


def env_without_github_tokens(env: Mapping[str, str] | None = None) -> dict[str, str]:
"""Return a copy of *env* with ambient GitHub CLI token variables removed."""
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: Mapping[str, str] | None = None) -> str | None:
"""Return an ambient GitHub token using the same precedence as the GitHub CLI."""
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 run_gh_command(
command: Sequence[str],
*,
retry_with_github_token: bool = True,
env: Mapping[str, str] | None = None,
**kwargs: Any,
) -> subprocess.CompletedProcess[Any]:
"""
Run a ``gh`` command using stored ``gh auth login`` credentials before ambient token env vars.

Locally, ``GH_TOKEN``/``GITHUB_TOKEN`` can shadow the user's normal GitHub CLI login. We first
run with those variables removed, then retry with the original environment only when that fails.
"""
command_env = os.environ.copy() if env is None else dict(env)
check = kwargs.pop("check", False)
result = subprocess.run(command, env=env_without_github_tokens(command_env), check=False, **kwargs)
if result.returncode == 0:
return result
if not retry_with_github_token or not get_github_token_from_env(command_env):
if check:
result.check_returncode()
return result
return subprocess.run(command, env=command_env, check=check, **kwargs)


def retrieve_github_token(token: str | None = None, *, env: Mapping[str, str] | None = None) -> str | None:
"""
Resolve a GitHub token for local Breeze commands.

Explicit ``--github-token`` values are preserved. Ambient ``GH_TOKEN``/``GITHUB_TOKEN`` values are
used only after trying the user's stored ``gh auth login`` credential.
"""
env_token = get_github_token_from_env(env)
source_env = os.environ if env is None else env
env_tokens = {
source_env[token_env_var] for token_env_var in GITHUB_TOKEN_ENV_VARS if source_env.get(token_env_var)
}
if token and token not in env_tokens:
Comment thread
leeyspaul marked this conversation as resolved.
return token
try:
gh_token_result = run_gh_command(
["gh", "auth", "token"],
capture_output=True,
text=True,
check=False,
retry_with_github_token=False,
env=env,
)
except FileNotFoundError:
return token or env_token
if gh_token_result.returncode == 0 and gh_token_result.stdout.strip():
return gh_token_result.stdout.strip()
return token or env_token


def get_ga_output(name: str, value: Any) -> str:
output_name = name.replace("_", "-")
Expand Down
17 changes: 9 additions & 8 deletions dev/breeze/src/airflow_breeze/utils/provider_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@
)
from airflow_breeze.utils.ci_group import ci_group
from airflow_breeze.utils.console import console_print
from airflow_breeze.utils.github import download_constraints_file, get_active_airflow_versions, get_tag_date
from airflow_breeze.utils.github import (
download_constraints_file,
get_active_airflow_versions,
get_tag_date,
retrieve_github_token,
)
from airflow_breeze.utils.packages import get_provider_distributions_metadata
from airflow_breeze.utils.path_utils import (
AIRFLOW_PYPROJECT_TOML_FILE_PATH,
Expand All @@ -48,7 +53,6 @@
PROVIDER_DEPENDENCIES_JSON_HASH_PATH,
PROVIDER_DEPENDENCIES_JSON_PATH,
)
from airflow_breeze.utils.run_utils import run_command
from airflow_breeze.utils.shared_options import get_verbose

_regenerate_provider_deps_lock = Lock()
Expand Down Expand Up @@ -248,12 +252,9 @@ def get_all_constraint_files_and_airflow_releases(
shutil.rmtree(CONSTRAINTS_CACHE_PATH, ignore_errors=True)
if not CONSTRAINTS_CACHE_PATH.exists():
if not github_token:
gh_auth_command = run_command(
["gh", "auth", "token"], check=False, capture_output=True, text=True
)
if gh_auth_command.returncode == 0:
console_print("\n[info]Retrieved GitHub token from gh auth token command[/]\n")
github_token = gh_auth_command.stdout.strip()
github_token = retrieve_github_token()
if github_token:
console_print("\n[info]Resolved GitHub token for constraints refresh[/]\n")
else:
console_print(
"[error]You need to provide GITHUB_TOKEN to generate providers metadata.[/]\n\n"
Expand Down
Loading
Loading