Repository: elastic/apm-agent-python

Issue:
[META 388] Collect Azure App Service cloud metadata
Implement https://github.com/elastic/apm/issues/388


Discussion:

Resolving patch:
diff --git a/elasticapm/utils/cloud.py b/elasticapm/utils/cloud.py
--- a/elasticapm/utils/cloud.py
+++ b/elasticapm/utils/cloud.py
@@ -164,5 +164,30 @@ def azure_metadata():
         return ret
 
     except Exception:
-        # Not on an Azure box
+        # Not on an Azure box, maybe an azure app service?
+        return azure_app_service_metadata()
+
+
+def azure_app_service_metadata():
+    ret = {"provider": "azure"}
+    website_owner_name = os.environ.get("WEBSITE_OWNER_NAME")
+    website_instance_id = os.environ.get("WEBSITE_INSTANCE_ID")
+    website_site_name = os.environ.get("WEBSITE_SITE_NAME")
+    website_resource_group = os.environ.get("WEBSITE_RESOURCE_GROUP")
+    if not all((website_owner_name, website_instance_id, website_site_name, website_resource_group)):
+        return {}
+    # Format of website_owner_name: {subscription id}+{app service plan resource group}-{region}webspace{.*}
+    if "+" not in website_owner_name:
+        return {}
+    try:
+        account_id, website_owner_name = website_owner_name.split("+")
+        ret["account"] = {"id": account_id}
+        region, _ = website_owner_name.split("webspace")
+        ret["region"] = region.rsplit("-", 1)[1]
+    except Exception:
         return {}
+
+    ret["instance"] = {"id": website_instance_id, "name": website_site_name}
+    ret["project"] = {"name": website_resource_group}
+
+    return ret


Test patch:
diff --git a/tests/utils/cloud_tests.py b/tests/utils/cloud_tests.py
--- a/tests/utils/cloud_tests.py
+++ b/tests/utils/cloud_tests.py
@@ -28,6 +28,8 @@
 #  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 #  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+import os
+
 import mock
 import urllib3
 
@@ -131,3 +133,63 @@ def request(self, *args, **kwargs):
         "provider": "azure",
         "region": "westus2",
     }
+
+
+def test_azure_app_service_metadata():
+    env_template = {
+        "WEBSITE_RESOURCE_GROUP": "resource_group",
+        "WEBSITE_SITE_NAME": "site_name",
+        "WEBSITE_INSTANCE_ID": "instance_id",
+    }
+    for owner_name in (
+        "f5940f10-2e30-3e4d-a259-63451ba6dae4+elastic-apm-AustraliaEastwebspace",
+        "f5940f10-2e30-3e4d-a259-63451ba6dae4+appsvc_linux_australiaeast-AustraliaEastwebspace-Linux",
+    ):
+        env = env_template.copy()
+        env["WEBSITE_OWNER_NAME"] = owner_name
+        with mock.patch.dict(os.environ, env):
+            metadata = elasticapm.utils.cloud.azure_app_service_metadata()
+            assert metadata == {
+                "account": {"id": "f5940f10-2e30-3e4d-a259-63451ba6dae4"},
+                "instance": {"id": "instance_id", "name": "site_name"},
+                "project": {"name": "resource_group"},
+                "provider": "azure",
+                "region": "AustraliaEast",
+            }
+
+
+def test_azure_app_service_metadata_wrong_format():
+    env = {
+        # the - before "elastic" is faulty
+        "WEBSITE_OWNER_NAME": "f5940f10-2e30-3e4d-a259-63451ba6dae4-elastic-apm-AustraliaEastwebspace",
+        "WEBSITE_RESOURCE_GROUP": "resource_group",
+        "WEBSITE_SITE_NAME": "site_name",
+        "WEBSITE_INSTANCE_ID": "instance_id",
+    }
+    with mock.patch.dict(os.environ, env):
+        metadata = elasticapm.utils.cloud.azure_app_service_metadata()
+        assert metadata == {}
+
+
+def test_azure_app_service_metadata_empty():
+    with mock.patch.dict(os.environ, {}):
+        metadata = elasticapm.utils.cloud.azure_app_service_metadata()
+        assert metadata == {}
+
+
+def test_azure_app_service_metadata_not_complete():
+    env_template = {
+        "WEBSITE_OWNER_NAME": "f5940f10-2e30-3e4d-a259-63451ba6dae4+elastic-apm-AustraliaEastwebspace",
+        "WEBSITE_RESOURCE_GROUP": "resource_group",
+        "WEBSITE_SITE_NAME": "site_name",
+        "WEBSITE_INSTANCE_ID": "instance_id",
+    }
+    for k in env_template.keys():
+        env = env_template.copy()
+        with mock.patch.dict(os.environ, env):
+            os.environ[k] = ""
+            metadata = elasticapm.utils.cloud.azure_app_service_metadata()
+            assert metadata == {}
+            del os.environ[k]
+            metadata = elasticapm.utils.cloud.azure_app_service_metadata()
+            assert metadata == {}