Skip to content

Commit 4b1719e

Browse files
Merge pull request #20 from khushishah513/function-comments
resolving comments on PR
2 parents c8f08e2 + 9b20d74 commit 4b1719e

File tree

3 files changed

+142
-149
lines changed

3 files changed

+142
-149
lines changed

src/containerapp/azext_containerapp/_clients.py

Lines changed: 18 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,6 @@ def list(cls, cmd, resource_group_name, container_app_name):
305305

306306
class ContainerAppFunctionsPreviewClient():
307307
api_version = "2025-10-02-preview"
308-
APP_INSIGHTS_API_VERSION = "2018-04-20"
309308

310309
@classmethod
311310
def list_functions_by_revision(cls, cmd, resource_group_name, container_app_name, revision_name):
@@ -379,114 +378,9 @@ def get_function(cls, cmd, resource_group_name, container_app_name, function_nam
379378
raise CLIError(f"Error retrieving function '{function_name}' for container app '{container_app_name}'.")
380379
return r.json()
381380

382-
@classmethod
383-
def get_function_invocation_summary(cls, cmd, resource_group_name, container_app_name, revision_name, function_name, timespan="30d"):
384-
# Fetch the app insights resource app id
385-
app_id = cls._get_app_insights_id(cmd, resource_group_name, container_app_name, revision_name)
386-
387-
# Use application insights query to get function invocations summary
388-
invocation_summary_query = (
389-
f"requests | extend functionNameFromCustomDimension = tostring(customDimensions['faas.name']) "
390-
f"| where timestamp >= ago({timespan}) "
391-
f"| where cloud_RoleName =~ '{container_app_name}' "
392-
f"| where cloud_RoleInstance contains '{revision_name}' "
393-
f"| where operation_Name =~ '{function_name}' or functionNameFromCustomDimension =~ '{function_name}' "
394-
f"| summarize SuccessCount = coalesce(countif(success == true), 0), ErrorCount = coalesce(countif(success == false), 0)"
395-
)
396-
397-
try:
398-
result = cls._execute_app_insights_query(cmd, app_id, invocation_summary_query, "getLast30DaySummary")
399-
return result
400-
except Exception as ex:
401-
raise CLIError(f"Error retrieving function invocation summary: {str(ex)}")
402-
403-
@classmethod
404-
def get_function_invocation_traces(cls, cmd, resource_group_name, container_app_name, revision_name, function_name, timespan="30d", limit=20):
405-
# Fetch the app insights resource app id
406-
app_id = cls._get_app_insights_id(cmd, resource_group_name, container_app_name, revision_name)
407-
408-
# Use application insights query to get function invocations traces
409-
invocation_traces_query = (
410-
f"requests | extend functionNameFromCustomDimension = tostring(customDimensions['faas.name']) "
411-
f"| project timestamp, id, operation_Name, success, resultCode, duration, operation_Id, functionNameFromCustomDimension, "
412-
f"cloud_RoleName, cloud_RoleInstance, invocationId=coalesce(tostring(customDimensions['InvocationId']), tostring(customDimensions['faas.invocation_id'])) "
413-
f"| where timestamp > ago({timespan}) "
414-
f"| where cloud_RoleName =~ '{container_app_name}' "
415-
f"| where cloud_RoleInstance contains '{revision_name}' "
416-
f"| where operation_Name =~ '{function_name}' or functionNameFromCustomDimension =~ '{function_name}' "
417-
f"| order by timestamp desc | take {limit} "
418-
f"| project timestamp, success, resultCode, durationInMilliSeconds=duration, invocationId, operationId=operation_Id, operationName=operation_Name, functionNameFromCustomDimension "
419-
)
420-
421-
try:
422-
result = cls._execute_app_insights_query(cmd, app_id, invocation_traces_query, "getInvocationTraces")
423-
return result
424-
except Exception as ex:
425-
raise CLIError(f"Error retrieving function invocation traces: {str(ex)}")
426-
427-
@classmethod
428-
def _get_app_insights_id(cls, cmd, resource_group_name, container_app_name, revision_name):
429-
# Fetch the revision details using the container app client
430-
revision = ContainerAppPreviewClient.show_revision(cmd, resource_group_name, container_app_name, revision_name)
431-
# Extract the list of environment variables from the revision's properties
432-
env_vars = []
433-
if revision and "properties" in revision and "template" in revision["properties"]:
434-
containers = revision["properties"]["template"].get("containers", [])
435-
for container in containers:
436-
env_vars.extend(container.get("env", []))
437-
438-
# Check for APPLICATIONINSIGHTS_CONNECTION_STRING
439-
ai_conn_str = None
440-
for env in env_vars:
441-
if env.get("name") == "APPLICATIONINSIGHTS_CONNECTION_STRING":
442-
ai_conn_str = env.get("value")
443-
break
444-
445-
if not ai_conn_str:
446-
raise CLIError(f"Required application setting APPLICATIONINSIGHTS_CONNECTION_STRING not present in the containerapp '{container_app_name}'.")
447-
448-
# Extract ApplicationId from the connection string
449-
app_id = None
450-
parts = ai_conn_str.split(";")
451-
for part in parts:
452-
if part.startswith("ApplicationId="):
453-
app_id = part.split("=", 1)[1]
454-
break
455-
456-
if not app_id:
457-
raise CLIError(f"ApplicationId not found in APPLICATIONINSIGHTS_CONNECTION_STRING for containerapp '{container_app_name}'.")
458-
return app_id
459-
460-
@classmethod
461-
def _execute_app_insights_query(cls, cmd, app_id, query, queryType, timespan="30D"):
462-
463-
# Application Insights REST API endpoint
464-
api_endpoint = "https://api.applicationinsights.io"
465-
url = f"{api_endpoint}/v1/apps/{app_id}/query?api-version={cls.APP_INSIGHTS_API_VERSION}&queryType={queryType}"
466-
467-
# Prepare the request body
468-
body = {
469-
"query": query,
470-
"timespan": f"P{timespan}"
471-
}
472-
473-
# Execute the query using Azure CLI's send_raw_request
474-
response = send_raw_request(
475-
cmd.cli_ctx,
476-
"POST",
477-
url,
478-
body=json.dumps(body),
479-
headers=["Content-Type=application/json"]
480-
)
481-
482-
result = response.json()
483-
if isinstance(result, dict) and 'error' in result:
484-
raise CLIError(f"Error retrieving invocations details: {result['error']}")
485-
return result
486-
487381
@classmethod
488382
def show_function_keys(cls, cmd, resource_group_name, name, key_type, key_name, function_name=None, revision_name=None, replica_name=None, container_name=None):
489-
from .custom import containerapp_debug
383+
from ._utils import execute_function_admin_command
490384

491385
command_fmt = ""
492386
if key_type != "functionKey":
@@ -496,22 +390,22 @@ def show_function_keys(cls, cmd, resource_group_name, name, key_type, key_name,
496390
command_fmt = "/bin/azure-functions-admin keys show --key-type {} --key-name {} --function-name {}"
497391
command = command_fmt.format(key_type, key_name, function_name)
498392

499-
r = containerapp_debug(
393+
r = execute_function_admin_command(
500394
cmd=cmd,
501395
resource_group_name=resource_group_name,
502396
name=name,
503-
container=container_name,
504-
revision=revision_name,
505-
replica=replica_name,
506-
debug_command=command
397+
command=command,
398+
revision_name=revision_name,
399+
replica_name=replica_name,
400+
container_name=container_name
507401
)
508402
if not r:
509403
raise CLIError(f"Error retrieving function key '{key_name}' of type '{key_type}'.")
510404
return r
511405

512406
@classmethod
513407
def list_function_keys(cls, cmd, resource_group_name, name, key_type, function_name=None, revision_name=None, replica_name=None, container_name=None):
514-
from .custom import containerapp_debug
408+
from ._utils import execute_function_admin_command
515409

516410
command_fmt = ""
517411
if key_type != "functionKey":
@@ -521,14 +415,14 @@ def list_function_keys(cls, cmd, resource_group_name, name, key_type, function_n
521415
command_fmt = "/bin/azure-functions-admin keys list --key-type {} --function-name {}"
522416
command = command_fmt.format(key_type, function_name)
523417

524-
r = containerapp_debug(
418+
r = execute_function_admin_command(
525419
cmd=cmd,
526420
resource_group_name=resource_group_name,
527421
name=name,
528-
container=container_name,
529-
revision=revision_name,
530-
replica=replica_name,
531-
debug_command=command
422+
command=command,
423+
revision_name=revision_name,
424+
replica_name=replica_name,
425+
container_name=container_name
532426
)
533427
if not r:
534428
raise CLIError(f"Error retrieving function keys of type '{key_type}'.")
@@ -537,7 +431,7 @@ def list_function_keys(cls, cmd, resource_group_name, name, key_type, function_n
537431
@classmethod
538432
def set_function_keys(cls, cmd, resource_group_name, name, key_type, key_name, key_value, function_name=None, revision_name=None, replica_name=None, container_name=None):
539433
"""Set/Update function keys based on key type"""
540-
from .custom import containerapp_debug
434+
from ._utils import execute_function_admin_command
541435

542436
command_fmt = ""
543437
if key_type != "functionKey":
@@ -550,14 +444,14 @@ def set_function_keys(cls, cmd, resource_group_name, name, key_type, key_name, k
550444
if key_value is not None:
551445
command += " --key-value {}".format(key_value)
552446

553-
r = containerapp_debug(
447+
r = execute_function_admin_command(
554448
cmd=cmd,
555449
resource_group_name=resource_group_name,
556450
name=name,
557-
container=container_name,
558-
revision=revision_name,
559-
replica=replica_name,
560-
debug_command=command
451+
command=command,
452+
revision_name=revision_name,
453+
replica_name=replica_name,
454+
container_name=container_name
561455
)
562456
if not r:
563457
raise CLIError(f"Error setting function key '{key_name}' of type '{key_type}'.")

src/containerapp/azext_containerapp/_utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,3 +875,17 @@ def get_random_replica(cmd, resource_group_name, container_app_name, revision_na
875875
raise CLIError(f"Could not determine replica name for revision '{revision_name}' of container app '{container_app_name}'.")
876876

877877
return replica_name, container_name
878+
879+
880+
def execute_function_admin_command(cmd, resource_group_name, name, command, revision_name=None, replica_name=None, container_name=None):
881+
from .custom import containerapp_debug
882+
883+
return containerapp_debug(
884+
cmd=cmd,
885+
resource_group_name=resource_group_name,
886+
name=name,
887+
container=container_name,
888+
revision=revision_name,
889+
replica=replica_name,
890+
debug_command=command
891+
)

src/containerapp/azext_containerapp/containerapp_functions_decorator.py

Lines changed: 110 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55
# --------------------------------------------------------------------------------------------
66
# pylint: disable=line-too-long, broad-except, logging-format-interpolation, too-many-public-methods, too-many-boolean-expressions, logging-fstring-interpolation
77

8+
import json
89
from knack.log import get_logger
910

10-
from azure.cli.core.azclierror import ValidationError
11+
from azure.cli.core.azclierror import ValidationError, CLIError
12+
from azure.cli.core.util import send_raw_request
1113
from azure.cli.command_modules.containerapp.base_resource import BaseResource
1214

1315
from ._client_factory import handle_raw_exception
1416
from ._validators import validate_basic_arguments, validate_revision_and_get_name, validate_functionapp_kind
1517
from ._transformers import process_app_insights_response
18+
from ._clients import ContainerAppPreviewClient
1619

1720
logger = get_logger(__name__)
1821

@@ -165,6 +168,8 @@ def show(self):
165168
class ContainerAppFunctionInvocationsDecorator(ContainerAppFunctionsDecorator):
166169
"""Decorator for showing function invocation"""
167170

171+
APP_INSIGHTS_API_VERSION = "2018-04-20"
172+
168173
def validate_arguments(self):
169174
"""Validate arguments required for function invocation operations"""
170175
validate_basic_arguments(
@@ -190,21 +195,92 @@ def validate_arguments(self):
190195
self.set_argument_revision_name(revision_name)
191196
self.validate_function_name_requirement()
192197

198+
def _get_app_insights_id(self, resource_group_name, container_app_name, revision_name):
199+
# Fetch the revision details using the container app client
200+
revision = ContainerAppPreviewClient.show_revision(self.cmd, resource_group_name, container_app_name, revision_name)
201+
# Extract the list of environment variables from the revision's properties
202+
env_vars = []
203+
if revision and "properties" in revision and "template" in revision["properties"]:
204+
containers = revision["properties"]["template"].get("containers", [])
205+
for container in containers:
206+
env_vars.extend(container.get("env", []))
207+
208+
# Check for APPLICATIONINSIGHTS_CONNECTION_STRING
209+
ai_conn_str = None
210+
for env in env_vars:
211+
if env.get("name") == "APPLICATIONINSIGHTS_CONNECTION_STRING":
212+
ai_conn_str = env.get("value")
213+
break
214+
215+
if not ai_conn_str:
216+
raise CLIError(f"Required application setting APPLICATIONINSIGHTS_CONNECTION_STRING not present in the containerapp '{container_app_name}'.")
217+
218+
# Extract ApplicationId from the connection string
219+
app_id = None
220+
parts = ai_conn_str.split(";")
221+
for part in parts:
222+
if part.startswith("ApplicationId="):
223+
app_id = part.split("=", 1)[1]
224+
break
225+
226+
if not app_id:
227+
raise CLIError(f"ApplicationId not found in APPLICATIONINSIGHTS_CONNECTION_STRING for containerapp '{container_app_name}'.")
228+
return app_id
229+
230+
def _execute_app_insights_query(self, app_id, query, query_type, timespan="30D"):
231+
# Application Insights REST API endpoint
232+
api_endpoint = "https://api.applicationinsights.io"
233+
url = f"{api_endpoint}/v1/apps/{app_id}/query?api-version={self.APP_INSIGHTS_API_VERSION}&queryType={query_type}"
234+
235+
# Prepare the request body
236+
body = {
237+
"query": query,
238+
"timespan": f"P{timespan}"
239+
}
240+
241+
# Execute the query using Azure CLI's send_raw_request
242+
response = send_raw_request(
243+
self.cmd.cli_ctx,
244+
"POST",
245+
url,
246+
body=json.dumps(body),
247+
headers=["Content-Type=application/json"]
248+
)
249+
250+
result = response.json()
251+
if isinstance(result, dict) and 'error' in result:
252+
raise CLIError(f"Error retrieving invocations details: {result['error']}")
253+
return result
254+
193255
def get_summary(self):
194256
"""Get function invocation summary using the client"""
195257
try:
196258
self.validate_arguments()
197259

198-
response = self.client.get_function_invocation_summary(
199-
cmd=self.cmd,
200-
resource_group_name=self.get_argument_resource_group_name(),
201-
container_app_name=self.get_argument_container_app_name(),
202-
revision_name=self.get_argument_revision_name(),
203-
function_name=self.get_argument_function_name(),
204-
timespan=self.get_argument_timespan() or "30d"
260+
# Get arguments
261+
cmd = self.cmd
262+
resource_group_name = self.get_argument_resource_group_name()
263+
container_app_name = self.get_argument_container_app_name()
264+
revision_name = self.get_argument_revision_name()
265+
function_name = self.get_argument_function_name()
266+
timespan = self.get_argument_timespan() or "30d"
267+
268+
# Fetch the app insights resource app id
269+
app_id = self._get_app_insights_id(resource_group_name, container_app_name, revision_name)
270+
271+
# Use application insights query to get function invocations summary
272+
invocation_summary_query = (
273+
f"requests | extend functionNameFromCustomDimension = tostring(customDimensions['faas.name']) "
274+
f"| where timestamp >= ago({timespan}) "
275+
f"| where cloud_RoleName =~ '{container_app_name}' "
276+
f"| where cloud_RoleInstance contains '{revision_name}' "
277+
f"| where operation_Name =~ '{function_name}' or functionNameFromCustomDimension =~ '{function_name}' "
278+
f"| summarize SuccessCount = coalesce(countif(success == true), 0), ErrorCount = coalesce(countif(success == false), 0)"
205279
)
206280

207-
return process_app_insights_response(response)
281+
result = self._execute_app_insights_query(app_id, invocation_summary_query, "getLast30DaySummary")
282+
283+
return process_app_insights_response(result)
208284
except Exception as e:
209285
handle_raw_exception(e)
210286

@@ -214,23 +290,32 @@ def get_traces(self):
214290
self.validate_arguments()
215291

216292
# Get all arguments
217-
resource_group = self.get_argument_resource_group_name()
218-
container_app = self.get_argument_container_app_name()
219-
revision = self.get_argument_revision_name()
220-
function = self.get_argument_function_name()
221-
timespan = self.get_argument_timespan()
222-
limit = self.get_argument_limit()
223-
224-
response = self.client.get_function_invocation_traces(
225-
cmd=self.cmd,
226-
resource_group_name=resource_group,
227-
container_app_name=container_app,
228-
revision_name=revision,
229-
function_name=function,
230-
timespan=timespan,
231-
limit=limit
293+
cmd = self.cmd
294+
resource_group_name = self.get_argument_resource_group_name()
295+
container_app_name = self.get_argument_container_app_name()
296+
revision_name = self.get_argument_revision_name()
297+
function_name = self.get_argument_function_name()
298+
timespan = self.get_argument_timespan() or "30d"
299+
limit = self.get_argument_limit() or 20
300+
301+
# Fetch the app insights resource app id
302+
app_id = self._get_app_insights_id(resource_group_name, container_app_name, revision_name)
303+
304+
# Use application insights query to get function invocations traces
305+
invocation_traces_query = (
306+
f"requests | extend functionNameFromCustomDimension = tostring(customDimensions['faas.name']) "
307+
f"| project timestamp, id, operation_Name, success, resultCode, duration, operation_Id, functionNameFromCustomDimension, "
308+
f"cloud_RoleName, cloud_RoleInstance, invocationId=coalesce(tostring(customDimensions['InvocationId']), tostring(customDimensions['faas.invocation_id'])) "
309+
f"| where timestamp > ago({timespan}) "
310+
f"| where cloud_RoleName =~ '{container_app_name}' "
311+
f"| where cloud_RoleInstance contains '{revision_name}' "
312+
f"| where operation_Name =~ '{function_name}' or functionNameFromCustomDimension =~ '{function_name}' "
313+
f"| order by timestamp desc | take {limit} "
314+
f"| project timestamp, success, resultCode, durationInMilliSeconds=duration, invocationId, operationId=operation_Id, operationName=operation_Name, functionNameFromCustomDimension "
232315
)
233316

234-
return process_app_insights_response(response)
317+
result = self._execute_app_insights_query(app_id, invocation_traces_query, "getInvocationTraces")
318+
319+
return process_app_insights_response(result)
235320
except Exception as e:
236321
handle_raw_exception(e)

0 commit comments

Comments
 (0)