Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions cfa/cloudops/_function_app_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from typing import Callable, List, Optional, Tuple

import duckdb
from azure.identity import DefaultAzureCredential
from azure.mgmt.web import WebSiteManagementClient

from .auth import (
DefaultCredentialHandler,
Expand All @@ -22,6 +24,105 @@


class FunctionAppClient:
@classmethod
def get_configuration(
cls,
function_app_name: str,
resource_group: Optional[str] = None,
subscription_id: Optional[str] = None,
) -> Optional[dict]:
resource_group = resource_group or os.getenv("AZURE_RESOURCE_GROUP")
if resource_group is None:
raise ValueError(
"Resource group must be provided either as an argument or through the AZURE_RESOURCE_GROUP environment variable."
)
subscription_id = subscription_id or os.getenv("AZURE_SUBSCRIPTION_ID")
if subscription_id is None:
raise ValueError(
"Subscription ID must be provided either as an argument or through the AZURE_SUBSCRIPTION_ID environment variable."
)
credential = DefaultAzureCredential()
web_mgmt_client = WebSiteManagementClient(credential, subscription_id)
function_app_config = web_mgmt_client.web_apps.get_configuration(
resource_group, function_app_name
)
return function_app_config

@classmethod
def get_tags(
cls,
function_app_name: str,
resource_group: Optional[str] = None,
subscription_id: Optional[str] = None,
) -> Optional[dict]:
return cls.get_configuration(
function_app_name, resource_group, subscription_id
).additional_properties.get("tags", [])

@classmethod
def get_health_check_flag(
cls,
function_app_name: str,
resource_group: Optional[str] = None,
subscription_id: Optional[str] = None,
) -> Optional[dict]:
return (
cls.get_configuration(
function_app_name, resource_group, subscription_id
).health_check_path
is not None
)

@classmethod
def list_functions(
cls,
function_app_name: str,
resource_group: Optional[str] = None,
subscription_id: Optional[str] = None,
) -> Optional[dict]:
resource_group = resource_group or os.getenv("AZURE_RESOURCE_GROUP")
if resource_group is None:
raise ValueError(
"Resource group must be provided either as an argument or through the AZURE_RESOURCE_GROUP environment variable."
)
subscription_id = subscription_id or os.getenv("AZURE_SUBSCRIPTION_ID")
if subscription_id is None:
raise ValueError(
"Subscription ID must be provided either as an argument or through the AZURE_SUBSCRIPTION_ID environment variable."
)
credential = DefaultAzureCredential()
web_mgmt_client = WebSiteManagementClient(credential, subscription_id)
function_list = []
for function in web_mgmt_client.web_apps.list_functions(
resource_group, function_app_name
):
function_list.append(function.as_dict())
return function_list

@classmethod
def get_function_details(
cls,
function_app_name: str,
function_name: str,
resource_group: Optional[str] = None,
subscription_id: Optional[str] = None,
) -> Optional[dict]:
resource_group = resource_group or os.getenv("AZURE_RESOURCE_GROUP")
if resource_group is None:
raise ValueError(
"Resource group must be provided either as an argument or through the AZURE_RESOURCE_GROUP environment variable."
)
subscription_id = subscription_id or os.getenv("AZURE_SUBSCRIPTION_ID")
if subscription_id is None:
raise ValueError(
"Subscription ID must be provided either as an argument or through the AZURE_SUBSCRIPTION_ID environment variable."
)
credential = DefaultAzureCredential()
web_mgmt_client = WebSiteManagementClient(credential, subscription_id)
return web_mgmt_client.web_apps.get_function(
resource_group, function_app_name, function_name
)

def __init__(
self,
function_app_name: Optional[str] = None,
Expand Down
4 changes: 3 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
The versioning pattern is `major.minor.patch`.

---
## v0.3.20
## v0.3.21
- added metadata lookup methods for Azure Function Apps to the `FunctionAppClient` module

## v0.3.20
- Added managed identity login to `list_acr_tags`

## v0.3.19
Expand Down
83 changes: 83 additions & 0 deletions docs/FunctionAppClient/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,89 @@ After creating the function app, please contact EDAV team and request permission
- Debugging the function app in Azure requires specific permissions. Currently, debugging is not supported directly. Users may need to work with the EDAV team for additional access if needed.
- Currently the Azure Function Manager can only be invoked from a Linux environment such as Bash shell, Git Bash or Windows Subsystem for Linux. In a future release, support shall be added for Windows Command prompt.

## 6. Scripts for getting metadata about exising Azure Function App

After successfully deploying a function app, you can use one of the following class methods on `FunctionAppClient` to retrieve configuration and runtime details about the function app. All these class methods expect Azure resource group and subscription ID to be provided. There are 2 ways of setting this up:

* set the `AZURE_RESOURCE_GROUP` and `AZURE_SUBSCRIPTION_ID` environment variables, or
* set the `resource_group` and `subscription_id` method parameters on each invocation

#### get_configuration

Retrieves the configuration details an app, such as platform version, runtime environment, handler mappings, IP security restrictions, default documents, virtual applications, Always On, etc.

_Example_:
```python
from cfa.cloudops import FunctionAppClient
FunctionAppClient.get_configuration(function_app_name='cfapredictafmprdfunc03')
```

_Response_:
```json
{'additional_properties': {'location': 'East US', 'tags': {'center': 'cfa', 'dateCreated': '2024-07-25 00:00:00', 'division': 'none', 'environment': 'prd', 'project': 'afm', 'requestor': ' ure7@cdc.gov', 'Purpose': 'Run Scheduled Job', 'costid': 'cfa', 'resourcename': 'cfapredictafmprdfunc03', 'issharedresource': 'no', 'owner': 'edav', 'createdby': 'none'}}, 'id': '/subscriptions/ef340bd6-2809-4635-b18b-7e6583a8803b/resourceGroups/EXT-EDAV-CFA-PRD/providers/Microsoft.Web/sites/cfapredictafmprdfunc03/config/web', 'name': 'cfapredictafmprdfunc03', 'kind': None, 'type': 'Microsoft.Web/sites/config', 'number_of_workers': 1, 'default_documents': ['Default.htm', 'Default.html', 'Default.asp', 'index.htm', 'index.html', 'iisstart.htm', 'default.aspx', 'index.php'], 'net_framework_version': 'v4.0', 'php_version': '', 'python_version': '', 'node_version': '', 'power_shell_version': '', 'linux_fx_version': 'PYTHON|3.9', 'windows_fx_version': None, 'request_tracing_enabled': False, 'request_tracing_expiration_time': None, 'remote_debugging_enabled': False, 'remote_debugging_version': None, 'http_logging_enabled': False, 'acr_use_managed_identity_creds': False, 'acr_user_managed_identity_id': None, 'logs_directory_size_limit': 35, 'detailed_error_logging_enabled': False, 'publishing_username': '$cfapredictafmprdfunc03', 'app_settings': None, 'metadata': None, 'connection_strings': None, 'machine_key': None, 'handler_mappings': None, 'document_root': None, 'scm_type': 'None', 'use32_bit_worker_process': False, 'web_sockets_enabled': False, 'always_on': True, 'java_version': None, 'java_container': None, 'java_container_version': None, 'app_command_line': '', 'managed_pipeline_mode': 'Integrated', 'virtual_applications': [<azure.mgmt.web.models._models_py3.VirtualApplication object at 0x789ab99aee00>], 'load_balancing': 'LeastRequests', 'experiments': <azure.mgmt.web.models._models_py3.Experiments object at 0x789ab99ac340>, 'limits': None, 'auto_heal_enabled': False, 'auto_heal_rules': None, 'tracing_options': None, 'vnet_name': '', 'vnet_route_all_enabled': False, 'vnet_private_ports_count': 0, 'cors': <azure.mgmt.web.models._models_py3.CorsSettings object at 0x789ab99ac5e0>, 'push': None, 'api_definition': None, 'api_management_config': None, 'auto_swap_slot_name': None, 'local_my_sql_enabled': False, 'managed_service_identity_id': None, 'x_managed_service_identity_id': None, 'key_vault_reference_identity': None, 'ip_security_restrictions': [<azure.mgmt.web.models._models_py3.IpSecurityRestriction object at 0x789ab99acfd0>], 'ip_security_restrictions_default_action': 'Allow', 'scm_ip_security_restrictions': [<azure.mgmt.web.models._models_py3.IpSecurityRestriction object at 0x789ab99aebc0>], 'scm_ip_security_restrictions_default_action': 'Allow', 'scm_ip_security_restrictions_use_main': False, 'http20_enabled': False, 'http20_proxy_flag': 0, 'min_tls_version': '1.2', 'min_tls_cipher_suite': None, 'scm_min_tls_version': '1.2', 'ftps_state': 'Disabled', 'pre_warmed_instance_count': 0, 'function_app_scale_limit': 0, 'elastic_web_app_scale_limit': None, 'health_check_path': '/api/HealthCheck', 'functions_runtime_scale_monitoring_enabled': False, 'website_time_zone': None, 'minimum_elastic_instance_count': 1, 'azure_storage_accounts': {}, 'public_network_access': 'Enabled'}
{'location': 'East US', 'tags': {'center': 'cfa', 'dateCreated': '2024-07-25 00:00:00', 'division': 'none', 'environment': 'prd', 'project': 'afm', 'requestor': ' ure7@cdc.gov', 'Purpose': 'Run Scheduled Job', 'costid': 'cfa', 'resourcename': 'cfapredictafmprdfunc03', 'issharedresource': 'no', 'owner': 'edav', 'createdby': 'none'}}
```

#### get_function_details

Fetch details of specific function within a function app. You can invoke the `list_functions` method to obtain list of function names from the function app.

_Example_:
```python
from cfa.cloudops import FunctionAppClient
FunctionAppClient.get_function_details(function_app_name='cfapredictafmprdfunc03', function_name='cfatimer')
```

_Response_:
```json
{'additional_properties': {'location': 'East US'}, 'id': '/subscriptions/ef340bd6-2809-4635-b18b-7e6583a8803b/resourceGroups/EXT-EDAV-CFA-PRD/providers/Microsoft.Web/sites/cfapredictafmprdfunc03/functions/cfatimer', 'name': 'cfapredictafmprdfunc03/cfatimer', 'kind': None, 'type': 'Microsoft.Web/sites/functions', 'function_app_id': None, 'script_root_path_href': None, 'script_href': 'https://cfapredictafmprdfunc03.cfa-prd-app.appserviceenvironment.net/admin/vfs/home/site/wwwroot/function_app.py', 'config_href': None, 'test_data_href': 'https://cfapredictafmprdfunc03.cfa-prd-app.appserviceenvironment.net/admin/vfs/home/data/Functions/sampledata/cfatimer.dat', 'secrets_file_href': None, 'href': 'https://cfapredictafmprdfunc03.cfa-prd-app.appserviceenvironment.net/admin/functions/cfatimer', 'config': {'name': 'cfatimer', 'entryPoint': 'cfatimer', 'scriptFile': 'function_app.py', 'language': 'python', 'functionDirectory': '/home/site/wwwroot', 'bindings': [{'direction': 'IN', 'type': 'timerTrigger', 'name': 'cfatimer', 'schedule': '%CFANotificationV2CRON%', 'runOnStartup': False, 'useMonitor': True}]}, 'files': None, 'test_data': '', 'invoke_url_template': None, 'language': 'python', 'is_disabled': False}
```

#### get_health_check_flag

Retrieves the current status (True or False) of health check enabled on a function app.

_Example_:
```python
from cfa.cloudops import FunctionAppClient
FunctionAppClient.get_health_check_flag(function_app_name='cfapredictafmprdfunc03')
```

_Response_:
```text
True
```

#### get_tags

Retrieves the tags assigned to an app. This is a subset of the response returned from `get_configuration` method.

_Example_:
```python
from cfa.cloudops import FunctionAppClient
FunctionAppClient.get_tags(function_app_name='cfapredictafmprdfunc03')
```

_Response_:
```json
{'location': 'East US', 'tags': {'center': 'cfa', 'dateCreated': '2024-07-25 00:00:00', 'division': 'none', 'environment': 'prd', 'project': 'afm', 'requestor': ' ure7@cdc.gov', 'Purpose': 'Run Scheduled Job', 'costid': 'cfa', 'resourcename': 'cfapredictafmprdfunc03', 'issharedresource': 'no', 'owner': 'edav', 'createdby': 'none'}}
```

#### list_functions

Retrieves list of all functions packaged and deployed to the specified function app.

_Example_:
```python
from cfa.cloudops import FunctionAppClient
FunctionAppClient.list_functions(function_app_name='cfapredictafmprdfunc03')
```

_Response_:
```json
[{'id': '/subscriptions/ef340bd6-2809-4635-b18b-7e6583a8803b/resourceGroups/EXT-EDAV-CFA-PRD/providers/Microsoft.Web/sites/cfapredictafmprdfunc03/functions/cfatimer', 'name': 'cfapredictafmprdfunc03/cfatimer', 'type': 'Microsoft.Web/sites/functions', 'script_href': 'https://cfapredictafmprdfunc03.cfa-prd-app.appserviceenvironment.net/admin/vfs/home/site/wwwroot/function_app.py', 'test_data_href': 'https://cfapredictafmprdfunc03.cfa-prd-app.appserviceenvironment.net/admin/vfs/home/data/Functions/sampledata/cfatimer.dat', 'href': 'https://cfapredictafmprdfunc03.cfa-prd-app.appserviceenvironment.net/admin/functions/cfatimer', 'config': {'name': 'cfatimer', 'entryPoint': 'cfatimer', 'scriptFile': 'function_app.py', 'language': 'python', 'functionDirectory': '/home/site/wwwroot', 'bindings': [{'direction': 'IN', 'type': 'timerTrigger', 'name': 'cfatimer', 'schedule': '%CFANotificationV2CRON%', 'runOnStartup': False, 'useMonitor': True}]}, 'test_data': '', 'language': 'python', 'is_disabled': False}, {'id': '/subscriptions/ef340bd6-2809-4635-b18b-7e6583a8803b/resourceGroups/EXT-EDAV-CFA-PRD/providers/Microsoft.Web/sites/cfapredictafmprdfunc03/functions/HealthCheck', 'name': 'cfapredictafmprdfunc03/HealthCheck', 'type': 'Microsoft.Web/sites/functions', 'script_href': 'https://cfapredictafmprdfunc03.cfa-prd-app.appserviceenvironment.net/admin/vfs/home/site/wwwroot/function_app.py', 'test_data_href': 'https://cfapredictafmprdfunc03.cfa-prd-app.appserviceenvironment.net/admin/vfs/home/data/Functions/sampledata/HealthCheck.dat', 'href': 'https://cfapredictafmprdfunc03.cfa-prd-app.appserviceenvironment.net/admin/functions/HealthCheck', 'config': {'name': 'HealthCheck', 'entryPoint': 'HealthCheck', 'scriptFile': 'function_app.py', 'language': 'python', 'functionDirectory': '/home/site/wwwroot', 'bindings': [{'direction': 'IN', 'type': 'httpTrigger', 'name': 'req', 'authLevel': 'ANONYMOUS', 'route': 'HealthCheck'}, {'direction': 'OUT', 'type': 'http', 'name': '$return'}]}, 'test_data': '', 'invoke_url_template': 'https://cfapredictafmprdfunc03.cfa-prd-app.appserviceenvironment.net/api/healthcheck', 'language': 'python', 'is_disabled': False}]
```

### Conclusion
By following this guide, you should be able to set up and use the CFA Azure Function test effectively. Ensure that all environment variables are correctly set and that your Service Principal has the necessary permissions.

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "cfa.cloudops"
version = "0.3.20"
version = "0.3.21"
description = "Cloud storage, batch, functions, MLOps assistance"
authors = [
{name = "Ryan Raasch", email = "xng3@cdc.gov"}
Expand Down
Loading