diff --git a/newrelic/common/utilization.py b/newrelic/common/utilization.py index 22b158e3ec..d96d9d9aa8 100644 --- a/newrelic/common/utilization.py +++ b/newrelic/common/utilization.py @@ -27,8 +27,6 @@ _logger = logging.getLogger(__name__) VALID_CHARS_RE = re.compile(r"[0-9a-zA-Z_ ./-]") -AZURE_RESOURCE_GROUP_NAME_RE = re.compile(r"\+([a-zA-Z0-9\-]+)-[a-zA-Z0-9]+(?:-Linux)") -AZURE_RESOURCE_GROUP_NAME_PARTIAL_RE = re.compile(r"\+([a-zA-Z0-9\-]+)(?:-Linux)?-[a-zA-Z0-9]+") class UtilizationHttpClient(InsecureHttpClient): @@ -233,18 +231,25 @@ class AzureFunctionUtilization(CommonUtilization): HEADERS = {"Metadata": "true"} # noqa: RUF012 VENDOR_NAME = "azurefunction" - @staticmethod - def fetch(): + @classmethod + def fetch(cls): cloud_region = os.environ.get("REGION_NAME") website_owner_name = os.environ.get("WEBSITE_OWNER_NAME") azure_function_app_name = os.environ.get("WEBSITE_SITE_NAME") + resource_group_name = os.environ.get("WEBSITE_RESOURCE_GROUP") + + if all((cloud_region, website_owner_name, azure_function_app_name, resource_group_name)): + subscription_id = "" + if "+" in website_owner_name: + subscription_id = website_owner_name.split("+")[0] + # Catch missing subscription_id or missing + + if not subscription_id: + _logger.debug( + "Unable to determine Azure Functions subscription id from WEBSITE_OWNER_NAME. %r", + website_owner_name, + ) + return None - if all((cloud_region, website_owner_name, azure_function_app_name)): - if website_owner_name.endswith("-Linux"): - resource_group_name = AZURE_RESOURCE_GROUP_NAME_RE.search(website_owner_name).group(1) - else: - resource_group_name = AZURE_RESOURCE_GROUP_NAME_PARTIAL_RE.search(website_owner_name).group(1) - subscription_id = re.search(r"(?:(?!\+).)*", website_owner_name).group(0) faas_app_name = f"/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}/providers/Microsoft.Web/sites/{azure_function_app_name}" # Only send if all values are present return (faas_app_name, cloud_region) diff --git a/newrelic/hooks/framework_azurefunctions.py b/newrelic/hooks/framework_azurefunctions.py index 968e235e50..d76725afca 100644 --- a/newrelic/hooks/framework_azurefunctions.py +++ b/newrelic/hooks/framework_azurefunctions.py @@ -13,7 +13,6 @@ # limitations under the License. import os -import re import urllib.parse as urlparse from newrelic.api.application import application_instance @@ -21,7 +20,6 @@ from newrelic.api.web_transaction import WebTransaction from newrelic.common.object_wrapper import wrap_function_wrapper from newrelic.common.signature import bind_args -from newrelic.common.utilization import AZURE_RESOURCE_GROUP_NAME_PARTIAL_RE, AZURE_RESOURCE_GROUP_NAME_RE def original_agent_instance(): @@ -35,18 +33,12 @@ def intrinsics_populator(application, context): trigger_type = "http" website_owner_name = os.environ.get("WEBSITE_OWNER_NAME", None) - if not website_owner_name: - subscription_id = "Unknown" - else: - subscription_id = re.search(r"(?:(?!\+).)*", website_owner_name) and re.search( - r"(?:(?!\+).)*", website_owner_name - ).group(0) - if website_owner_name and website_owner_name.endswith("-Linux"): - resource_group_name = AZURE_RESOURCE_GROUP_NAME_RE.search(website_owner_name).group(1) - elif website_owner_name: - resource_group_name = AZURE_RESOURCE_GROUP_NAME_PARTIAL_RE.search(website_owner_name).group(1) - else: - resource_group_name = os.environ.get("WEBSITE_RESOURCE_GROUP", "Unknown") + + subscription_id = "Unknown" + if website_owner_name and "+" in website_owner_name: + subscription_id = website_owner_name.split("+")[0] or "Unknown" + + resource_group_name = os.environ.get("WEBSITE_RESOURCE_GROUP", "Unknown") azure_function_app_name = os.environ.get("WEBSITE_SITE_NAME", getattr(application, "name", "Azure Function App")) cloud_resource_id = f"/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}/providers/Microsoft.Web/sites/{azure_function_app_name}/functions/{getattr(context, 'function_name', 'Unknown')}" diff --git a/tests/framework_azurefunctions/test_utilization.py b/tests/framework_azurefunctions/test_utilization.py new file mode 100644 index 0000000000..3bcc5f9f63 --- /dev/null +++ b/tests/framework_azurefunctions/test_utilization.py @@ -0,0 +1,39 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from newrelic.common.utilization import AzureFunctionUtilization + + +def test_utilization(monkeypatch): + monkeypatch.setenv("REGION_NAME", "EastUS2") + monkeypatch.setenv("WEBSITE_RESOURCE_GROUP", "testing-rg") + monkeypatch.setenv("WEBSITE_OWNER_NAME", "b999997b-cb91-49e0-b922-c9188372bdba+testing-rg-EastUS2webspace-Linux") + monkeypatch.setenv("WEBSITE_SITE_NAME", "test-func-linux") + + result = AzureFunctionUtilization.fetch() + assert result, "Failed to parse utilization for Azure Functions." + + faas_app_name, cloud_region = result + expected_faas_app_name = "/subscriptions/b999997b-cb91-49e0-b922-c9188372bdba/resourceGroups/testing-rg/providers/Microsoft.Web/sites/test-func-linux" + assert faas_app_name == expected_faas_app_name + assert cloud_region == "EastUS2" + + +def test_utilization_bad_website_owner_name(monkeypatch): + monkeypatch.setenv("REGION_NAME", "EastUS2") + monkeypatch.setenv("WEBSITE_OWNER_NAME", "ERROR") + monkeypatch.setenv("WEBSITE_SITE_NAME", "test-func-linux") + + result = AzureFunctionUtilization.fetch() + assert result is None, f"Expected failure but got result instead. {result}"