This is an automated email from the ASF dual-hosted git repository.

turaga 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 8b03304883c Fix list-envs auth status for env names containing .json 
(#64677)
8b03304883c is described below

commit 8b03304883ca6237ce538f1af9408e5a6b74ca18
Author: Henry Chen <[email protected]>
AuthorDate: Wed Apr 8 02:17:09 2026 +0800

    Fix list-envs auth status for env names containing .json (#64677)
---
 airflow-ctl/src/airflowctl/api/client.py              | 19 +++++++++++++++----
 .../src/airflowctl/ctl/commands/auth_command.py       |  6 +++---
 .../airflow_ctl/ctl/commands/test_auth_command.py     | 18 ++++++++++++++++++
 3 files changed, 36 insertions(+), 7 deletions(-)

diff --git a/airflow-ctl/src/airflowctl/api/client.py 
b/airflow-ctl/src/airflowctl/api/client.py
index 0ef5d7cb164..1f6a9bfd25e 100644
--- a/airflow-ctl/src/airflowctl/api/client.py
+++ b/airflow-ctl/src/airflowctl/api/client.py
@@ -168,6 +168,11 @@ class Credentials:
         """Generate path for the CLI config file."""
         return f"{self.api_environment}.json"
 
+    @staticmethod
+    def token_key_for_environment(api_environment: str) -> str:
+        """Build the keyring/debug token key for a given environment name."""
+        return f"api_token_{api_environment}"
+
     def save(self, skip_keyring: bool = False):
         """
         Save the credentials to keyring and URL to disk as a file.
@@ -185,7 +190,7 @@ class Credentials:
                 with open(
                     os.path.join(default_config_dir, 
f"debug_creds_{self.input_cli_config_file}"), "w"
                 ) as f:
-                    json.dump({f"api_token_{self.api_environment}": 
self.api_token}, f)
+                    
json.dump({self.token_key_for_environment(self.api_environment): 
self.api_token}, f)
             else:
                 if skip_keyring:
                     return
@@ -198,7 +203,11 @@ class Credentials:
                 for candidate in candidates:
                     if hasattr(candidate, "_get_new_password"):
                         candidate._get_new_password = _bounded_get_new_password
-                keyring.set_password("airflowctl", 
f"api_token_{self.api_environment}", self.api_token)  # type: ignore[arg-type]
+                keyring.set_password(
+                    "airflowctl",
+                    self.token_key_for_environment(self.api_environment),
+                    self.api_token,  # type: ignore[arg-type]
+                )
         except (NoKeyringError, NotImplementedError) as e:
             log.error(e)
             raise AirflowCtlKeyringException(
@@ -229,11 +238,13 @@ class Credentials:
                     )
                     with open(debug_creds_path) as df:
                         debug_credentials = json.load(df)
-                        self.api_token = 
debug_credentials.get(f"api_token_{self.api_environment}")
+                        self.api_token = debug_credentials.get(
+                            
self.token_key_for_environment(self.api_environment)
+                        )
                 else:
                     try:
                         self.api_token = keyring.get_password(
-                            "airflowctl", f"api_token_{self.api_environment}"
+                            "airflowctl", 
self.token_key_for_environment(self.api_environment)
                         )
                     except ValueError as e:
                         # Incorrect keyring password
diff --git a/airflow-ctl/src/airflowctl/ctl/commands/auth_command.py 
b/airflow-ctl/src/airflowctl/ctl/commands/auth_command.py
index 236b8d5c6b8..cf521cbe7ee 100644
--- a/airflow-ctl/src/airflowctl/ctl/commands/auth_command.py
+++ b/airflow-ctl/src/airflowctl/ctl/commands/auth_command.py
@@ -144,7 +144,7 @@ def list_envs(args) -> None:
         if filename.startswith("debug_creds_") or 
filename.endswith("_generated.json"):
             continue
 
-        env_name = filename.replace(".json", "")
+        env_name, _ = os.path.splitext(filename)
 
         # Try to read config file
         api_url = None
@@ -168,11 +168,11 @@ def list_envs(args) -> None:
                 if os.path.exists(debug_path):
                     with open(debug_path) as f:
                         debug_creds = json.load(f)
-                        if f"api_token_{env_name}" in debug_creds:
+                        if Credentials.token_key_for_environment(env_name) in 
debug_creds:
                             token_status = "authenticated"
             else:
                 # Check keyring
-                token = keyring.get_password("airflowctl", 
f"api_token_{env_name}")
+                token = keyring.get_password("airflowctl", 
Credentials.token_key_for_environment(env_name))
                 if token:
                     token_status = "authenticated"
         except NoKeyringError:
diff --git a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_auth_command.py 
b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_auth_command.py
index e76fafc28ad..2bda56b0fdc 100644
--- a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_auth_command.py
+++ b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_auth_command.py
@@ -477,3 +477,21 @@ class TestListEnvs:
 
             # Only production environment should be checked, not the special 
files
             mock_get_password.assert_called_once_with("airflowctl", 
"api_token_production")
+
+    def test_list_envs_environment_name_with_json_substring(self, monkeypatch):
+        """Test list-envs keeps '.json' substrings in environment name for key 
lookup."""
+        with (
+            tempfile.TemporaryDirectory() as temp_airflow_home,
+            patch("keyring.get_password") as mock_get_password,
+        ):
+            monkeypatch.setenv("AIRFLOW_HOME", temp_airflow_home)
+
+            with open(os.path.join(temp_airflow_home, 
"prod.json.region.json"), "w") as f:
+                json.dump({"api_url": "http://localhost:8080"}, f)
+
+            mock_get_password.return_value = "test_token"
+
+            args = self.parser.parse_args(["auth", "list-envs"])
+            auth_command.list_envs(args)
+
+            mock_get_password.assert_called_once_with("airflowctl", 
"api_token_prod.json.region")

Reply via email to