Skip to content

Commit 135f9fc

Browse files
author
Shikha Jha
committed
added enriched failure prompt for az webapp up
1 parent 0f35993 commit 135f9fc

3 files changed

Lines changed: 195 additions & 60 deletions

File tree

src/azure-cli/azure/cli/command_modules/appservice/_deployment_context_engine.py

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -79,36 +79,51 @@ def _get_app_plan_sku(cmd, resource_group_name, webapp_name):
7979
return "Unknown"
8080

8181

82-
def _determine_deployment_type(params):
83-
"""Infer the deployment mechanism from the params."""
84-
if params.src_url:
82+
def _determine_deployment_type(params=None, *, src_url=None, artifact_type=None):
83+
"""Infer the deployment mechanism from params object or explicit kwargs.
84+
85+
When *params* is supplied the values are read from it; explicit kwargs
86+
override the params-derived values when both are provided.
87+
"""
88+
_src_url = src_url if src_url is not None else (getattr(params, 'src_url', None) if params else None)
89+
_artifact = artifact_type if artifact_type is not None else (getattr(params, 'artifact_type', None) if params else None)
90+
91+
if _src_url:
8592
return "OneDeploy (URL-based)"
86-
artifact = getattr(params, 'artifact_type', None)
87-
if artifact == 'zip':
93+
if _artifact == 'zip':
8894
return "ZipDeploy"
89-
if artifact == 'war':
95+
if _artifact == 'war':
9096
return "WarDeploy"
91-
if artifact == 'jar':
97+
if _artifact == 'jar':
9298
return "JarDeploy"
93-
if artifact == 'ear':
99+
if _artifact == 'ear':
94100
return "EarDeploy"
95-
if artifact == 'startup':
101+
if _artifact == 'startup':
96102
return "StartupFile"
97-
if artifact == 'static':
103+
if _artifact == 'static':
98104
return "StaticDeploy"
99105
return "OneDeploy"
100106

101107

102-
def build_enriched_error_context(params, status_code=None, error_message=None,
108+
def build_enriched_error_context(params=None, *, cmd=None, resource_group_name=None,
109+
webapp_name=None, slot=None, src_url=None,
110+
artifact_type=None, status_code=None, error_message=None,
103111
deployment_status=None, deployment_properties=None,
104112
last_known_step=None, kudu_status=None):
105113
"""
106114
Build a structured context-enriched error dict for a deployment failure.
107115
116+
Accepts either a *params* object (``OneDeployParams``) **or** individual
117+
keyword arguments — callers that already have a params object can keep
118+
passing it; callers in code-paths that don't (e.g. zipdeploy) can pass
119+
the relevant values directly. Explicit kwargs override params values.
120+
108121
Parameters
109122
----------
110-
params : OneDeployParams
123+
params : OneDeployParams, optional
111124
The deployment parameters object.
125+
cmd, resource_group_name, webapp_name, slot, src_url, artifact_type :
126+
Individual app-context values; used when *params* is not supplied.
112127
status_code : int, optional
113128
HTTP status code of the failed response.
114129
error_message : str, optional
@@ -127,6 +142,14 @@ def build_enriched_error_context(params, status_code=None, error_message=None,
127142
dict
128143
Structured error context ready for display.
129144
"""
145+
# Normalise — extract from params when available, explicit kwargs win
146+
_cmd = cmd or (params.cmd if params else None)
147+
_rg = resource_group_name or (params.resource_group_name if params else None)
148+
_name = webapp_name or (params.webapp_name if params else None)
149+
_slot = slot if slot is not None else (getattr(params, 'slot', None) if params else None)
150+
_src_url = src_url if src_url is not None else (getattr(params, 'src_url', None) if params else None)
151+
_artifact = artifact_type if artifact_type is not None else (getattr(params, 'artifact_type', None) if params else None)
152+
130153
pattern = match_failure_pattern(
131154
status_code=status_code,
132155
error_message=error_message,
@@ -144,11 +167,18 @@ def build_enriched_error_context(params, status_code=None, error_message=None,
144167
context["stage"] = deployment_status or "Unknown"
145168

146169
# App metadata (best-effort)
147-
context["runtime"] = _get_app_runtime(params.cmd, params.resource_group_name,
148-
params.webapp_name, params.slot)
149-
context["deploymentType"] = _determine_deployment_type(params)
150-
context["region"] = _get_app_region(params.cmd, params.resource_group_name, params.webapp_name)
151-
context["planSku"] = _get_app_plan_sku(params.cmd, params.resource_group_name, params.webapp_name)
170+
if _cmd and _rg and _name:
171+
context["runtime"] = _get_app_runtime(_cmd, _rg, _name, _slot)
172+
context["region"] = _get_app_region(_cmd, _rg, _name)
173+
context["planSku"] = _get_app_plan_sku(_cmd, _rg, _name)
174+
else:
175+
context["runtime"] = "Unknown"
176+
context["region"] = "Unknown"
177+
context["planSku"] = "Unknown"
178+
179+
context["deploymentType"] = _determine_deployment_type(
180+
params, src_url=_src_url, artifact_type=_artifact
181+
)
152182

153183
# Causes and fixes
154184
if pattern:
@@ -158,9 +188,9 @@ def build_enriched_error_context(params, status_code=None, error_message=None,
158188
context["commonCauses"] = ["Unrecognised failure — see error details below"]
159189
context["suggestedFixes"] = [
160190
"Check deployment logs: 'az webapp log deployment show -n {} -g {}'".format(
161-
params.webapp_name, params.resource_group_name),
191+
_name or '<app>', _rg or '<rg>'),
162192
"Check runtime logs: 'az webapp log tail -n {} -g {}'".format(
163-
params.webapp_name, params.resource_group_name)
193+
_name or '<app>', _rg or '<rg>')
164194
]
165195

166196
# Extra diagnostics
@@ -251,16 +281,25 @@ def format_enriched_error_message(context):
251281
return "\n".join(lines)
252282

253283

254-
def raise_enriched_deployment_error(params, status_code=None, error_message=None,
284+
def raise_enriched_deployment_error(params=None, *, cmd=None, resource_group_name=None,
285+
webapp_name=None, slot=None, src_url=None,
286+
artifact_type=None, status_code=None, error_message=None,
255287
deployment_status=None, deployment_properties=None,
256288
last_known_step=None, kudu_status=None):
257289
"""
258290
Build context-enriched diagnostics and raise a CLIError.
259291
260292
This is the main entry-point called from the deployment code paths.
293+
Accepts either a *params* object or individual keyword arguments.
261294
"""
262295
context = build_enriched_error_context(
263296
params=params,
297+
cmd=cmd,
298+
resource_group_name=resource_group_name,
299+
webapp_name=webapp_name,
300+
slot=slot,
301+
src_url=src_url,
302+
artifact_type=artifact_type,
264303
status_code=status_code,
265304
error_message=error_message,
266305
deployment_status=deployment_status,

src/azure-cli/azure/cli/command_modules/appservice/custom.py

Lines changed: 83 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,9 @@ def enable_zip_deploy(cmd, resource_group_name, name, src, timeout=None, slot=No
881881
app_is_linux_webapp = is_linux_webapp(app)
882882
app_is_function_app = is_functionapp(app)
883883

884+
# Should we enrich deployment errors with context? (webapp only, not functionapp)
885+
_should_enrich_errors = not app_is_function_app
886+
884887
# Read file content
885888
with open(os.path.realpath(os.path.expanduser(src)), 'rb') as fs:
886889
zip_content = fs.read()
@@ -913,13 +916,26 @@ def enable_zip_deploy(cmd, resource_group_name, name, src, timeout=None, slot=No
913916
# check the status of async deployment
914917
if res.status_code == 202:
915918
response_body = None
916-
if track_status:
917-
response_body = _check_runtimestatus_with_deploymentstatusapi(cmd, resource_group_name, name, slot,
918-
deployment_status_url, is_async=True,
919-
timeout=timeout)
920-
else:
921-
response_body = _check_zip_deployment_status(cmd, resource_group_name, name, deployment_status_url,
922-
slot, timeout)
919+
try:
920+
if track_status:
921+
response_body = _check_runtimestatus_with_deploymentstatusapi(cmd, resource_group_name, name, slot,
922+
deployment_status_url, is_async=True,
923+
timeout=timeout)
924+
else:
925+
response_body = _check_zip_deployment_status(cmd, resource_group_name, name, deployment_status_url,
926+
slot, timeout)
927+
except CLIError as deploy_err:
928+
if _should_enrich_errors:
929+
raise_enriched_deployment_error(
930+
cmd=cmd,
931+
resource_group_name=resource_group_name,
932+
webapp_name=name,
933+
slot=slot,
934+
artifact_type="zip",
935+
error_message=str(deploy_err),
936+
last_known_step="Zip deployment accepted (HTTP 202), tracking status"
937+
)
938+
raise
923939
return response_body
924940

925941
# check if there's an ongoing process
@@ -934,6 +950,18 @@ def enable_zip_deploy(cmd, resource_group_name, name, src, timeout=None, slot=No
934950

935951
# check if an error occured during deployment
936952
if res.status_code:
953+
if _should_enrich_errors:
954+
raise_enriched_deployment_error(
955+
cmd=cmd,
956+
resource_group_name=resource_group_name,
957+
webapp_name=name,
958+
slot=slot,
959+
artifact_type="zip",
960+
status_code=res.status_code,
961+
error_message=res.text if res.text else None,
962+
last_known_step="Zip deployment HTTP request",
963+
kudu_status=str(res.status_code)
964+
)
937965
raise AzureInternalError("An error occured during deployment. Status Code: {}, Details: {}"
938966
.format(res.status_code, res.text))
939967

@@ -9852,12 +9880,14 @@ def _make_onedeploy_request(params):
98529880
params.webapp_name,
98539881
deployment_status_url, params.slot, params.timeout)
98549882
except CLIError as deploy_err:
9855-
# Enrich the downstream deployment-tracking error with context
9856-
raise_enriched_deployment_error(
9857-
params=params,
9858-
error_message=str(deploy_err),
9859-
last_known_step="Deployment accepted (HTTP 200/202), tracking status"
9860-
)
9883+
if not params.is_functionapp:
9884+
# Enrich the downstream deployment-tracking error with context
9885+
raise_enriched_deployment_error(
9886+
params=params,
9887+
error_message=str(deploy_err),
9888+
last_known_step="Deployment accepted (HTTP 200/202), tracking status"
9889+
)
9890+
raise
98619891
logger.info('Server response: %s', response_body)
98629892
else:
98639893
if 'application/json' in response.headers.get('content-type', ""):
@@ -9880,15 +9910,22 @@ def _make_onedeploy_request(params):
98809910
"starting a new deployment. You can track the ongoing deployment at {}"
98819911
.format(deployment_status_url))
98829912

9883-
# check if an error occurred during deployment — raise context-enriched error
9913+
# check if an error occurred during deployment
98849914
if response.status_code:
9885-
raise_enriched_deployment_error(
9886-
params=params,
9887-
status_code=response.status_code,
9888-
error_message=response.text if response.text else None,
9889-
last_known_step="HTTP request sent to deployment API",
9890-
kudu_status=str(response.status_code)
9891-
)
9915+
if not params.is_functionapp:
9916+
raise_enriched_deployment_error(
9917+
params=params,
9918+
status_code=response.status_code,
9919+
error_message=response.text if response.text else None,
9920+
last_known_step="HTTP request sent to deployment API",
9921+
kudu_status=str(response.status_code)
9922+
)
9923+
scm_url = _get_scm_url(params.cmd, params.resource_group_name, params.webapp_name, params.slot)
9924+
latest_deploymentinfo_url = scm_url + "/api/deployments/latest"
9925+
raise CLIError("An error occurred during deployment. Status Code: {}, {} Please visit {}"
9926+
" to get more information about your deployment"
9927+
.format(response.status_code, f"Details: {response.text}," if response.text else "",
9928+
latest_deploymentinfo_url))
98929929

98939930

98949931
# OneDeploy
@@ -9909,27 +9946,33 @@ def _perform_onedeploy_internal(params):
99099946
# Check if this is already an enriched error (from raise_enriched_deployment_error)
99109947
if "COPILOT CONTEXT" in str(ex):
99119948
raise
9912-
# Raw CLIError from send_raw_request or other deployment calls — enrich it
9913-
raise_enriched_deployment_error(
9914-
params=params,
9915-
error_message=str(ex),
9916-
last_known_step="Deployment request"
9917-
)
9949+
if not params.is_functionapp:
9950+
# Raw CLIError from send_raw_request or other deployment calls — enrich it
9951+
raise_enriched_deployment_error(
9952+
params=params,
9953+
error_message=str(ex),
9954+
last_known_step="Deployment request"
9955+
)
9956+
raise
99189957
except HttpResponseError as ex:
9919-
# Azure SDK errors (e.g. Bad Request from ARM)
9920-
raise_enriched_deployment_error(
9921-
params=params,
9922-
status_code=ex.status_code if hasattr(ex, 'status_code') else None,
9923-
error_message=str(ex),
9924-
last_known_step="ARM deployment request"
9925-
)
9958+
if not params.is_functionapp:
9959+
# Azure SDK errors (e.g. Bad Request from ARM)
9960+
raise_enriched_deployment_error(
9961+
params=params,
9962+
status_code=ex.status_code if hasattr(ex, 'status_code') else None,
9963+
error_message=str(ex),
9964+
last_known_step="ARM deployment request"
9965+
)
9966+
raise
99269967
except Exception as ex: # pylint: disable=broad-except
9927-
# Catch-all for unexpected errors (connection errors, timeouts, etc.)
9928-
raise_enriched_deployment_error(
9929-
params=params,
9930-
error_message=str(ex),
9931-
last_known_step="Deployment request"
9932-
)
9968+
if not params.is_functionapp:
9969+
# Catch-all for unexpected errors (connection errors, timeouts, etc.)
9970+
raise_enriched_deployment_error(
9971+
params=params,
9972+
error_message=str(ex),
9973+
last_known_step="Deployment request"
9974+
)
9975+
raise
99339976

99349977

99359978
def _wait_for_webapp(tunnel_server):

src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_deployment_context_engine.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,24 @@ def test_determine_deployment_type_war(self):
189189
params = _make_mock_params(artifact_type="war", src_url=None)
190190
self.assertEqual(_determine_deployment_type(params), "WarDeploy")
191191

192+
def test_determine_deployment_type_kwargs_zip(self):
193+
"""kwargs-only calling convention (no params object)."""
194+
self.assertEqual(_determine_deployment_type(artifact_type="zip"), "ZipDeploy")
195+
196+
def test_determine_deployment_type_kwargs_url(self):
197+
self.assertEqual(
198+
_determine_deployment_type(src_url="https://example.com/app.zip"),
199+
"OneDeploy (URL-based)"
200+
)
201+
202+
def test_determine_deployment_type_kwargs_override(self):
203+
"""Explicit kwargs should override params values."""
204+
params = _make_mock_params(artifact_type="war", src_url=None)
205+
self.assertEqual(
206+
_determine_deployment_type(params, artifact_type="jar"),
207+
"JarDeploy"
208+
)
209+
192210
def test_build_context_with_known_pattern(self):
193211
self._patch_app_metadata()
194212
params = _make_mock_params()
@@ -289,6 +307,41 @@ def test_raise_enriched_deployment_error(self):
289307
self.assertIn("ZipDeployTimeout", str(cm.exception))
290308
self.assertIn("COPILOT CONTEXT", str(cm.exception))
291309

310+
def test_raise_enriched_deployment_error_kwargs_only(self):
311+
"""Call raise_enriched_deployment_error with kwargs instead of params."""
312+
self._patch_app_metadata()
313+
mock_cmd = MagicMock()
314+
mock_cmd.cli_ctx = MagicMock()
315+
from knack.util import CLIError
316+
with self.assertRaises(CLIError) as cm:
317+
raise_enriched_deployment_error(
318+
cmd=mock_cmd,
319+
resource_group_name="test-rg",
320+
webapp_name="test-app",
321+
artifact_type="zip",
322+
status_code=504,
323+
error_message="Gateway Timeout"
324+
)
325+
self.assertIn("ZipDeployTimeout", str(cm.exception))
326+
self.assertIn("COPILOT CONTEXT", str(cm.exception))
327+
self.assertIn("ZipDeploy", str(cm.exception))
328+
329+
def test_build_context_kwargs_only(self):
330+
"""Call build_enriched_error_context with kwargs instead of params."""
331+
self._patch_app_metadata()
332+
mock_cmd = MagicMock()
333+
mock_cmd.cli_ctx = MagicMock()
334+
ctx = build_enriched_error_context(
335+
cmd=mock_cmd,
336+
resource_group_name="test-rg",
337+
webapp_name="test-app",
338+
artifact_type="zip",
339+
status_code=504,
340+
error_message="Gateway Timeout"
341+
)
342+
self.assertEqual(ctx["errorCode"], "ZipDeployTimeout")
343+
self.assertEqual(ctx["deploymentType"], "ZipDeploy")
344+
292345

293346
# ---------------------------------------------------------------------------
294347
# Integration-level test: verify the full error flow

0 commit comments

Comments
 (0)