Skip to content

Commit f51fa96

Browse files
xiangyan99micromaomaopvaneck
authored
Fix #26857: separate stdout from stderr in AzureCliCredential (#26953) (#27076)
* Fix #26857: separate stdout from stderr in AzureCliCredential * Fix unit tests for AzureCliCredential * Fix #26857 for aio.AzureCliCredential * azure_cli.py: Fix types in _run_command * Fix test_cli_credential_async.py * Update sdk/identity/azure-identity/CHANGELOG.md Co-authored-by: Paul Van Eck <[email protected]> Co-authored-by: Tingmao Wang <[email protected]> Co-authored-by: Paul Van Eck <[email protected]> Co-authored-by: Xiang Yan <[email protected]> Co-authored-by: maowtm <[email protected]> Co-authored-by: Tingmao Wang <[email protected]> Co-authored-by: Paul Van Eck <[email protected]>
1 parent c88fb38 commit f51fa96

File tree

5 files changed

+34
-32
lines changed

5 files changed

+34
-32
lines changed

sdk/identity/azure-identity/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Bugs Fixed
66

7+
- `AzureCliCredential` now works even when `az` prints warnings to stderr. ([#26857](https://github.com/Azure/azure-sdk-for-python/issues/26857))
78
- Fixed issue where user-supplied `TokenCachePersistenceOptions` weren't propagated when using `SharedTokenCacheCredential` ([#26982](https://github.com/Azure/azure-sdk-for-python/issues/26982))
89

910
### Breaking Changes

sdk/identity/azure-identity/azure/identity/_credentials/azure_cli.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def _run_command(command):
143143
working_directory = get_safe_working_dir()
144144

145145
kwargs = {
146-
"stderr": subprocess.STDOUT,
146+
"stderr": subprocess.PIPE,
147147
"cwd": working_directory,
148148
"universal_newlines": True,
149149
"env": dict(os.environ, AZURE_CORE_NO_COLOR="true"),
@@ -154,14 +154,14 @@ def _run_command(command):
154154
return subprocess.check_output(args, **kwargs)
155155
except subprocess.CalledProcessError as ex:
156156
# non-zero return from shell
157-
if ex.returncode == 127 or ex.output.startswith("'az' is not recognized"):
157+
if ex.returncode == 127 or ex.stderr.startswith("'az' is not recognized"):
158158
raise CredentialUnavailableError(message=CLI_NOT_FOUND)
159-
if "az login" in ex.output or "az account set" in ex.output:
159+
if "az login" in ex.stderr or "az account set" in ex.stderr:
160160
raise CredentialUnavailableError(message=NOT_LOGGED_IN)
161161

162162
# return code is from the CLI -> propagate its output
163-
if ex.output:
164-
message = sanitize_output(ex.output)
163+
if ex.stderr:
164+
message = sanitize_output(ex.stderr)
165165
else:
166166
message = "Failed to invoke Azure CLI"
167167
raise ClientAuthenticationError(message=message)

sdk/identity/azure-identity/azure/identity/aio/_credentials/azure_cli.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,13 @@ async def _run_command(command: str) -> str:
100100
proc = await asyncio.create_subprocess_exec(
101101
*args,
102102
stdout=asyncio.subprocess.PIPE,
103-
stderr=asyncio.subprocess.STDOUT,
103+
stderr=asyncio.subprocess.PIPE,
104104
cwd=working_directory,
105105
env=dict(os.environ, AZURE_CORE_NO_COLOR="true")
106106
)
107-
stdout, _ = await asyncio.wait_for(proc.communicate(), 10)
108-
output = stdout.decode()
107+
stdout_b, stderr_b = await asyncio.wait_for(proc.communicate(), 10)
108+
output = stdout_b.decode()
109+
stderr = stderr_b.decode()
109110
except OSError as ex:
110111
# failed to execute 'cmd' or '/bin/sh'; CLI may or may not be installed
111112
error = CredentialUnavailableError(message="Failed to execute '{}'".format(args[0]))
@@ -117,11 +118,11 @@ async def _run_command(command: str) -> str:
117118
if proc.returncode == 0:
118119
return output
119120

120-
if proc.returncode == 127 or output.startswith("'az' is not recognized"):
121+
if proc.returncode == 127 or stderr.startswith("'az' is not recognized"):
121122
raise CredentialUnavailableError(CLI_NOT_FOUND)
122123

123-
if "az login" in output or "az account set" in output:
124+
if "az login" in stderr or "az account set" in stderr:
124125
raise CredentialUnavailableError(message=NOT_LOGGED_IN)
125126

126-
message = sanitize_output(output) if output else "Failed to invoke Azure CLI"
127+
message = sanitize_output(stderr) if stderr else "Failed to invoke Azure CLI"
127128
raise ClientAuthenticationError(message=message)

sdk/identity/azure-identity/tests/test_cli_credential.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
)
3232

3333

34-
def raise_called_process_error(return_code, output, cmd="..."):
35-
error = subprocess.CalledProcessError(return_code, cmd=cmd, output=output)
34+
def raise_called_process_error(return_code, output="", cmd="...", stderr=""):
35+
error = subprocess.CalledProcessError(return_code, cmd=cmd, output=output, stderr=stderr)
3636
return mock.Mock(side_effect=error)
3737

3838

@@ -76,17 +76,17 @@ def test_get_token():
7676
def test_cli_not_installed_linux():
7777
"""The credential should raise CredentialUnavailableError when the CLI isn't installed"""
7878

79-
output = "/bin/sh: 1: az: not found"
80-
with mock.patch(CHECK_OUTPUT, raise_called_process_error(127, output)):
79+
stderr = "/bin/sh: 1: az: not found"
80+
with mock.patch(CHECK_OUTPUT, raise_called_process_error(127, stderr=stderr)):
8181
with pytest.raises(CredentialUnavailableError, match=CLI_NOT_FOUND):
8282
AzureCliCredential().get_token("scope")
8383

8484

8585
def test_cli_not_installed_windows():
8686
"""The credential should raise CredentialUnavailableError when the CLI isn't installed"""
8787

88-
output = "'az' is not recognized as an internal or external command, operable program or batch file."
89-
with mock.patch(CHECK_OUTPUT, raise_called_process_error(1, output)):
88+
stderr = "'az' is not recognized as an internal or external command, operable program or batch file."
89+
with mock.patch(CHECK_OUTPUT, raise_called_process_error(1, stderr=stderr)):
9090
with pytest.raises(CredentialUnavailableError, match=CLI_NOT_FOUND):
9191
AzureCliCredential().get_token("scope")
9292

@@ -102,18 +102,18 @@ def test_cannot_execute_shell():
102102
def test_not_logged_in():
103103
"""When the CLI isn't logged in, the credential should raise CredentialUnavailableError"""
104104

105-
output = "ERROR: Please run 'az login' to setup account."
106-
with mock.patch(CHECK_OUTPUT, raise_called_process_error(1, output)):
105+
stderr = "ERROR: Please run 'az login' to setup account."
106+
with mock.patch(CHECK_OUTPUT, raise_called_process_error(1, stderr=stderr)):
107107
with pytest.raises(CredentialUnavailableError, match=NOT_LOGGED_IN):
108108
AzureCliCredential().get_token("scope")
109109

110110

111111
def test_unexpected_error():
112112
"""When the CLI returns an unexpected error, the credential should raise an error containing the CLI's output"""
113113

114-
output = "something went wrong"
115-
with mock.patch(CHECK_OUTPUT, raise_called_process_error(42, output)):
116-
with pytest.raises(ClientAuthenticationError, match=output):
114+
stderr = "something went wrong"
115+
with mock.patch(CHECK_OUTPUT, raise_called_process_error(42, stderr=stderr)):
116+
with pytest.raises(ClientAuthenticationError, match=stderr):
117117
AzureCliCredential().get_token("scope")
118118

119119

@@ -181,7 +181,7 @@ def fake_check_output(command_line, **_):
181181

182182
token = AzureCliCredential(tenant_id=second_tenant).get_token("scope")
183183
assert token.token == second_token
184-
184+
185185
def test_multitenant_authentication():
186186
default_tenant = "first-tenant"
187187
first_token = "***"

sdk/identity/azure-identity/tests/test_cli_credential_async.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ async def test_get_token():
101101
async def test_cli_not_installed_linux():
102102
"""The credential should raise CredentialUnavailableError when the CLI isn't installed"""
103103

104-
output = "/bin/sh: 1: az: not found"
105-
with mock.patch(SUBPROCESS_EXEC, mock_exec(output, return_code=127)):
104+
stderr = "/bin/sh: 1: az: not found"
105+
with mock.patch(SUBPROCESS_EXEC, mock_exec("", stderr, return_code=127)):
106106
with pytest.raises(CredentialUnavailableError, match=CLI_NOT_FOUND):
107107
credential = AzureCliCredential()
108108
await credential.get_token("scope")
@@ -111,8 +111,8 @@ async def test_cli_not_installed_linux():
111111
async def test_cli_not_installed_windows():
112112
"""The credential should raise CredentialUnavailableError when the CLI isn't installed"""
113113

114-
output = "'az' is not recognized as an internal or external command, operable program or batch file."
115-
with mock.patch(SUBPROCESS_EXEC, mock_exec(output, return_code=1)):
114+
stderr = "'az' is not recognized as an internal or external command, operable program or batch file."
115+
with mock.patch(SUBPROCESS_EXEC, mock_exec("", stderr, return_code=1)):
116116
with pytest.raises(CredentialUnavailableError, match=CLI_NOT_FOUND):
117117
credential = AzureCliCredential()
118118
await credential.get_token("scope")
@@ -130,8 +130,8 @@ async def test_cannot_execute_shell():
130130
async def test_not_logged_in():
131131
"""When the CLI isn't logged in, the credential should raise CredentialUnavailableError"""
132132

133-
output = "ERROR: Please run 'az login' to setup account."
134-
with mock.patch(SUBPROCESS_EXEC, mock_exec(output, return_code=1)):
133+
stderr = "ERROR: Please run 'az login' to setup account."
134+
with mock.patch(SUBPROCESS_EXEC, mock_exec("", stderr, return_code=1)):
135135
with pytest.raises(CredentialUnavailableError, match=NOT_LOGGED_IN):
136136
credential = AzureCliCredential()
137137
await credential.get_token("scope")
@@ -140,9 +140,9 @@ async def test_not_logged_in():
140140
async def test_unexpected_error():
141141
"""When the CLI returns an unexpected error, the credential should raise an error containing the CLI's output"""
142142

143-
output = "something went wrong"
144-
with mock.patch(SUBPROCESS_EXEC, mock_exec(output, return_code=42)):
145-
with pytest.raises(ClientAuthenticationError, match=output):
143+
stderr = "something went wrong"
144+
with mock.patch(SUBPROCESS_EXEC, mock_exec("", stderr, return_code=42)):
145+
with pytest.raises(ClientAuthenticationError, match=stderr):
146146
credential = AzureCliCredential()
147147
await credential.get_token("scope")
148148

0 commit comments

Comments
 (0)