page_type | languages | products | description | urlFragment | ||||
---|---|---|---|---|---|---|---|---|
sample |
|
|
Getting the list of Resource Groups using Azure Functions and Azure Python SDK |
azure-functions-python-sample-list-resource-groups |
Automation using the Azure Function is one of the primary use cases of Azure Functions. Your Azure Functions app can be either a simple timer-based application to do cleanup or become an extensive Azure Event Grid based listener to do advanced data auditing and event-driven administration and automation. Azure Functions supports multiple trigger-types such as HTTP trigger, Timer trigger and Azure Event Grid trigger, which can help in administration and automation scenarios for Azure resources.
Here are some of the prerequisites to get this sample work for you.
For creating an Azure Function app in VSCode, please go through the Microsoft Docs tutorial on creating your first Azure Function using Visual Studio Code. In the code snippet along with the sample, we name the function GetListOfResourceGroups
, with the HTTP trigger. In the sample below, we will update the function.json
.
One you generate a new function app, the requirements.txt
should have the following dependencies added to it :
azure-functions
azure-mgmt-compute
azure-mgmt-resource
azure-identity
azure-cli-core
Setting up local.settings.json
would help you pass in the subscription Id
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=foobar;AccountKey=******==;EndpointSuffix=core.windows.net",
"FUNCTIONS_WORKER_RUNTIME": "python",
"AZURE_SUBSCRIPTION_ID": "8e03%%%%-%%%%-%%%%-%%%%-%%%%%%%%c15b"}
}
You need to toggle the system assigned identity flag to “On” here to get the app registered to interact with Azure resources using this managed identity .
[!NOTE] Please go through the Managed identities blog for more details on how to set up system identities and find more details.
Once you setup the application’s system identity, setup the role assignments of the service principle to give the app access to the subscription information. More details on assigning role to the app can be found in the documentation for assigning a role to the application.
The following application uses the above set managed identity to talk with Azure to get the resource groups list. We create an HTTP Trigger based function, with route setup and return a JSON list of resource groups information.
Here is the function.json
:
{ "scriptFile": "__init__.py",
"bindings": [{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get"],
"route": "resourcegroups"
},{
"type": "http",
"direction": "out",
"name": "$return"
}]}
As described in the best practices guide for Azure Functions, making your functions asynchronous is one of the keep your code performant for long IO calls. In the following sample, we use the async
and await
to get the resource group lists in an asynchronous fashion.
async def main(req: func.HttpRequest) -> func.HttpResponse:
"""
The main entry point to the function.
"""
if "MSI_ENDPOINT" in os.environ:
credentials = MSIAuthentication()
else:
credentials, *_ = get_azure_cli_credentials()
subscription_id = os.environ.get(
'AZURE_SUBSCRIPTION_ID', '11111111-1111-1111-1111-111111111111')
list_of_rgs = await list_rgs(credentials, subscription_id)
return func.HttpResponse(list_of_rgs, mimetype="application/json")
It is a good practice to modularize your code and separate out the operations into its own modules. This example puts all the resource groups related operation in its own module.
def process_rg_instance(group):
"""
Get the relevant pieces of information from a ResourceGroup instance.
"""
return {
"Name": group.name,
"Id": group.id,
"Location": group.location,
"Tags": group.tags,
"Properties": group.properties.provisioning_state if group.properties and group.properties.provisioning_state else None
}
async def list_rgs(credentials, subscription_id):
"""
Get list of resource groups for the subscription id passed.
"""
list_of_resource_groups = []
with ResourceManagementClient(credentials, subscription_id) as rg_client:
try:
for i in rg_client.resource_groups.list():
list_of_resource_groups.append(process_rg_instance(i))
except Exception as e:
logging.error("encountered: {0}".format(str(e)))
return json.dumps(list_of_resource_groups)
To test locally, start the debug mode and test the function using the HTTP endpoint exposed after the host and the worker load up the function.
Http Functions:
GetListOfResourceGroups: [GET] http://localhost:7071/api/resourcegroups
You can publish the function app directly from VSCode using the “Publish to Azure option” in the Azure extension. For more information, please refer the guide to publish the project to Azure using Visual Studio Code.
You can use one of these HTTP test tools to see the API in action locally, and on Azure:
- Visual Studio Code with an extension from Visual Studio Marketplace
- PowerShell Invoke-RestMethod
- Microsoft Edge - Network Console tool
- Bruno
- curl
Caution
For scenarios where you have sensitive data, such as credentials, secrets, access tokens, API keys, and other similar information, make sure to use a tool that protects your data with the necessary security features, works offline or locally, doesn't sync your data to the cloud, and doesn't require that you sign in to an online account. This way, you reduce the risk around exposing sensitive data to the public.
Running locally will help you to verify the credentials, configuration and business logic. If you find any issue, please check the Troubleshooting section below to help you solve the known issues.
This sample helps you setup your app to work with Azure resources using the Managed Service Identity (MSI) and get a list of resource groups in your subscription. This would help you get started with Azure functions to be used for Azure resource administration and automation scenario.
Here are some of the issues you might hit while running the sample:
Check the requirements.txt
file to make sure azure-cli-core
is installed.
5/20/2020 6:10:33 AM] Executed 'Functions.DeleteResourcesTimerFunction' (Failed, Id=c49aced0-c73a-45e0-9302-dc686cef5742)
[5/20/2020 6:10:33 AM] System.Private.CoreLib: Exception while executing function: Functions.DeleteResourcesTimerFunction. System.Private.CoreLib: Result: Failure
[5/20/2020 6:10:33 AM] Exception: ImportError: You need to install 'azure-cli-core' to load CLI credentials
[5/20/2020 6:10:33 AM] Stack: File "/usr/local/Cellar/azure-functions-core-tools@3/3.0.2534/workers/python/3.7/OSX/X64/azure_functions_worker/dispatcher.py", line 312, in _handle__invocation_request
[5/20/2020 6:10:33 AM] call_result = await fi.func(**args)
[5/20/2020 6:10:33 AM] File "/Users/varadmeru/work/microsoft/LinuxGARelated/cleanup-service-v2/DeleteResourcesTimerFunction/main.py", line 21, in main
[5/20/2020 6:10:33 AM] await asyncio.gather(cleanup())
[5/20/2020 6:10:33 AM] File "/Users/varadmeru/work/microsoft/LinuxGARelated/cleanup-service-v2/DeleteResourcesTimerFunction/cleanup.py", line 122, in cleanup
[5/20/2020 6:10:33 AM] credentials, *_ = get_azure_cli_credentials()
[5/20/2020 6:10:33 AM] File "/Users/varadmeru/work/microsoft/LinuxGARelated/cleanup-service-v2/.venv/lib/python3.7/site-packages/azure/common/credentials.py", line 97, in get_azure_cli_credentials
[5/20/2020 6:10:33 AM] profile = get_cli_profile()
[5/20/2020 6:10:33 AM] File "/Users/varadmeru/work/microsoft/LinuxGARelated/cleanup-service-v2/.venv/lib/python3.7/site-packages/azure/common/credentials.py", line 30, in get_cli_profile
[5/20/2020 6:10:33 AM] raise ImportError("You need to install 'azure-cli-core' to load CLI credentials")
If you are using the new DefaultAzureCredential - Authentication and the Azure SDK | Azure SDKs, you could see the following error showing up in the functions logs, even if your function doesn’t throw an Internal Server Error.
encountered: 'DefaultAzureCredential' object has no attribute 'signed_session'
Please use MSIAuthentication for the time being.
-
Seeing
FileNotFoundError: [Errno 2] No such file or directory: '/home/.azure/azureProfile.json'
in the invocation logs.The stack trace looks like the following:
Result: Failure Exception: FileNotFoundError: [Errno 2] No such file or directory: '/home/.azure/azureProfile.json' Stack: File "/azure-functions-host/workers/python/3.7/LINUX/X64/azure_functions_worker/dispatcher.py", line 311, in _handle__invocation_request call_result = await fi.func(**args) File "/home/site/wwwroot/GetListOfResourceGroups/__init__.py", line 20, in main credentials, *_ = get_azure_cli_credentials() File "/home/site/wwwroot/.python_packages/lib/site-packages/azure/common/credentials.py", line 97, in get_azure_cli_credentials profile = get_cli_profile() File "/home/site/wwwroot/.python_packages/lib/site-packages/azure/common/credentials.py", line 34, in get_cli_profile ACCOUNT.load(os.path.join(azure_folder, 'azureProfile.json')) File "/home/site/wwwroot/.python_packages/lib/site-packages/azure/cli/core/_session.py", line 61, in load self.save() File "/home/site/wwwroot/.python_packages/lib/site-packages/azure/cli/core/_session.py", line 65, in save with codecs_open(self.filename, 'w', encoding=self._encoding) as f: File "/usr/local/lib/python3.7/codecs.py", line 904, in open file = builtins.open(filename, mode, buffering)
This is due to the system assigned service principle not enabled yet. Check the perquisites.
-
Authorization failed message due to role assignment not present - please check pre-requisite #4 for the fix. Also confirm if the local settings are correctly reflecting in the portal (check the troubleshooting bullet below).
encountered: Azure Error: AuthorizationFailed Message: The client 'a7ad%%%%-%%%%-%%%%-%%%%-%%%%%%%%8c13' with object id 'a7ad%%%%-%%%%-%%%%-%%%%-%%%%%%%%8c13' does not have authorization to perform action 'Microsoft.Resources/subscriptions/resourcegroups/read' over scope '/subscriptions/8e03%%%%-%%%%-%%%%-%%%%-%%%%%%%%c15b' or the scope is invalid. If access was recently granted, please refresh your credentials.
-
Subscription not found - In our code, we fetch the subscription from the environment variable
subscription_id = os.environ.get( 'AZURE_SUBSCRIPTION_ID', '11111111-1111-1111-1111-111111111111')
You could Make sure your local setting are updated