From a46ab9359c7a0e292160117e5f7d1caab0c7993b Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Fri, 7 Jul 2023 14:56:09 +0000 Subject: [PATCH 1/4] Rename to function_app --- IconWriter/function.json | 20 -------------------- IconWriter/__init__.py => function_app.py | 0 2 files changed, 20 deletions(-) delete mode 100644 IconWriter/function.json rename IconWriter/__init__.py => function_app.py (100%) diff --git a/IconWriter/function.json b/IconWriter/function.json deleted file mode 100644 index d9e9952..0000000 --- a/IconWriter/function.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "scriptFile": "__init__.py", - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "get" - ] - }, - { - "dataType": "binary", - "type": "http", - "direction": "out", - "name": "$return" - } - ] -} \ No newline at end of file diff --git a/IconWriter/__init__.py b/function_app.py similarity index 100% rename from IconWriter/__init__.py rename to function_app.py From 0b47087fc469665bed31173e3a1536e8c438f6f2 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Sun, 9 Jul 2023 14:51:03 +0000 Subject: [PATCH 2/4] Upgrade to v2 --- .devcontainer/Dockerfile | 6 +++--- .devcontainer/devcontainer.json | 2 +- .gitignore | 1 - function_app.py | 5 +++++ local.settings.json | 7 +++++++ 5 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 local.settings.json diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3c42442..9c30165 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,9 +1,9 @@ -ARG VARIANT=bullseye -FROM --platform=amd64 mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT} +ARG IMAGE=bullseye +FROM --platform=amd64 mcr.microsoft.com/devcontainers/${IMAGE} RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg \ && mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg \ && sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/debian/$(lsb_release -rs | cut -d'.' -f 1)/prod $(lsb_release -cs) main" > /etc/apt/sources.list.d/dotnetdev.list' \ && apt-get update && apt-get install -y xdg-utils \ && apt-get update && apt-get install -y azure-functions-core-tools-4 \ && apt-get clean -y && rm -rf /var/lib/apt/lists/* -RUN curl -fsSL https://aka.ms/install-azd.sh | bash \ No newline at end of file +RUN curl -fsSL https://aka.ms/install-azd.sh | bash diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d30c635..acba9b9 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,7 @@ "build": { "dockerfile": "Dockerfile", "args": { - "IMAGE": "python:3.9" + "IMAGE": "python:3.10-bullseye" } }, "features": { diff --git a/.gitignore b/.gitignore index 8b40a36..2c28da9 100644 --- a/.gitignore +++ b/.gitignore @@ -126,7 +126,6 @@ dmypy.json bin obj appsettings.json -local.settings.json # Azurite artifacts __blobstorage__ diff --git a/function_app.py b/function_app.py index 76b5ee6..ff889df 100644 --- a/function_app.py +++ b/function_app.py @@ -3,6 +3,9 @@ import azure.functions as func from icon_writer import write_icon +app = func.FunctionApp() + + def get_param(req, param_name, default_value=None): param_value = req.params.get(param_name) if not param_value: @@ -14,6 +17,8 @@ def get_param(req, param_name, default_value=None): param_value = req_body.get(param_name) return param_value if param_value is not None else default_value +@app.function_name(name="IconWriter") +@app.route(route="IconWriter") def main(req: func.HttpRequest) -> func.HttpResponse: text = get_param(req, 'text') size = int(get_param(req, 'size', 80)) diff --git a/local.settings.json b/local.settings.json new file mode 100644 index 0000000..0c4181c --- /dev/null +++ b/local.settings.json @@ -0,0 +1,7 @@ +{ + "IsEncrypted": false, + "Values": { + "FUNCTIONS_WORKER_RUNTIME": "python", + "AzureWebJobsFeatureFlags": "EnableWorkerIndexing" + } +} \ No newline at end of file From fbbf5cfeb43f9b5de8e1142c61289b471ce3078f Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Sun, 9 Jul 2023 15:02:34 +0000 Subject: [PATCH 3/4] Fix infra --- infra/resources.bicep | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/infra/resources.bicep b/infra/resources.bicep index 30a74f1..23b5e86 100644 --- a/infra/resources.bicep +++ b/infra/resources.bicep @@ -72,7 +72,7 @@ resource functionApp 'Microsoft.Web/sites@2020-06-01' = { serverFarmId: hostingPlan.id clientAffinityEnabled: false siteConfig: { - linuxFxVersion: 'Python|3.9' + linuxFxVersion: 'Python|3.10' appSettings: [ { name: 'APPINSIGHTS_INSTRUMENTATIONKEY' @@ -90,6 +90,10 @@ resource functionApp 'Microsoft.Web/sites@2020-06-01' = { name: 'FUNCTIONS_WORKER_RUNTIME' value: 'python' } + { + name: 'AzureWebJobsFeatureFlags' + value: 'EnableWorkerIndexing' + } { name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${az.environment().suffixes.storage};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}' From 4fdc1906d05ce3d22d662d61ebce80b966e8b2a8 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Wed, 10 Apr 2024 10:30:32 -0700 Subject: [PATCH 4/4] APIM stuff --- README.md | 63 +++++++++++++++++++++++++++++++++++++ azure.yaml | 2 +- function_app.py | 2 +- infra/apimanagement.bicep | 1 + infra/app-diagnostics.bicep | 56 +++++++++++++++++++++++++++++++++ infra/main.bicep | 2 ++ infra/resources.bicep | 12 +++++++ 7 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 README.md create mode 100644 infra/app-diagnostics.bicep diff --git a/README.md b/README.md new file mode 100644 index 0000000..8964cee --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# Icon Writer (Function App) + +This repository contains a simple Azure Function App that can generate icons using Python and the Pillow library. + +The deployment step also creates an Azure API Management service to serve the function, to handle aspects like CORS, rate-limiting, and keys. + +## Opening the project + +This project has Dev Container support, so it will be automatically setup if you open it in Github Codespaces or in local VS Code with the Dev Containers extension. + +If you're unable to open the Dev Container, then you'll need to: + +1. Create a [Python virtual environment](https://docs.python.org/3/tutorial/venv.html#creating-virtual-environments) and activate it. + +2. Install requirements: + + ```shell + python3 -m pip install --user -r requirements.txt + ``` + +3. Install the [Azure Developer CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd). + +## Local development + +Use the local emulator from Azure Functions Core Tools to test the function locally. +(There is no local emulator for the API Management service). + +1. Open this repository in Github Codespaces or VS Code with Remote Dev Containers extension. +2. Open the Terminal and make sure you're in the root folder. +3. Run `func host start --python` +4. Click 'http://localhost:7071/api/IconWriter' in the terminal, which should open a website in a new tab. Change the URL to "/" to see the auto-generated documentation and try generating a map. + +## Deployment + +This repo is set up for deployment using the +[Azure Developer CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/overview), +which relies on the `azure.yaml` file and the configuration files in the `infra` folder. + +Steps for deployment: + +1. Sign up for a [free Azure account](https://azure.microsoft.com/free/) and create an Azure Subscription. +3. Login to Azure: + + ```shell + azd auth login + ``` + +3. Provision and deploy all the resources: + + ```shell + azd up + ``` + + It will prompt you to provide an `azd` environment name (like "staticmaps"), select a subscription from your Azure account, and select a location (like "eastus"). Then it will provision the resources in your account and deploy the latest code. + +4. Once it finishes deploying, navigate to the API endpoint URL from the output. Since the function is secured, you should see a 401 when navigating to the function endpoint. + +5. When you've made any changes to the app code, you can just run: + + ```shell + azd deploy + ``` + diff --git a/azure.yaml b/azure.yaml index bc69669..b97f7c8 100644 --- a/azure.yaml +++ b/azure.yaml @@ -6,4 +6,4 @@ services: api: project: . language: py - host: function \ No newline at end of file + host: function diff --git a/function_app.py b/function_app.py index ff889df..72b3faa 100644 --- a/function_app.py +++ b/function_app.py @@ -18,7 +18,7 @@ def get_param(req, param_name, default_value=None): return param_value if param_value is not None else default_value @app.function_name(name="IconWriter") -@app.route(route="IconWriter") +@app.route(route="IconWriter", methods=['GET']) def main(req: func.HttpRequest) -> func.HttpResponse: text = get_param(req, 'text') size = int(get_param(req, 'size', 80)) diff --git a/infra/apimanagement.bicep b/infra/apimanagement.bicep index dcf63aa..3f60f04 100644 --- a/infra/apimanagement.bicep +++ b/infra/apimanagement.bicep @@ -169,3 +169,4 @@ resource apimAPIDiagnostics 'Microsoft.ApiManagement/service/apis/diagnostics@20 } output apimServiceID string = apimService.id +output apimServiceUrl string = apimService.properties.gatewayUrl diff --git a/infra/app-diagnostics.bicep b/infra/app-diagnostics.bicep new file mode 100644 index 0000000..f084c87 --- /dev/null +++ b/infra/app-diagnostics.bicep @@ -0,0 +1,56 @@ +param appName string = '' + +@description('The kind of the app.') +@allowed([ + 'functionapp' + 'webapp' +]) +param kind string + +@description('Resource ID of log analytics workspace.') +param diagnosticWorkspaceId string + +param diagnosticLogCategoriesToEnable array = kind == 'functionapp' ? [ + 'FunctionAppLogs' +] : [ + 'AppServiceHTTPLogs' + 'AppServiceConsoleLogs' + 'AppServiceAppLogs' + 'AppServiceAuditLogs' + 'AppServiceIPSecAuditLogs' + 'AppServicePlatformLogs' +] + +@description('Optional. The name of metrics that will be streamed.') +@allowed([ + 'AllMetrics' +]) +param diagnosticMetricsToEnable array = [ + 'AllMetrics' +] + + +var diagnosticsLogs = [for category in diagnosticLogCategoriesToEnable: { + category: category + enabled: true +}] + +var diagnosticsMetrics = [for metric in diagnosticMetricsToEnable: { + category: metric + timeGrain: null + enabled: true +}] + +resource app 'Microsoft.Web/sites@2022-03-01' existing = { + name: appName +} + +resource app_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { + name: '${appName}-diagnostics' + scope: app + properties: { + workspaceId: diagnosticWorkspaceId + metrics: diagnosticsMetrics + logs: diagnosticsLogs + } +} diff --git a/infra/main.bicep b/infra/main.bicep index 61b770c..1f8a487 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -43,3 +43,5 @@ module resources 'resources.bicep' = { allowedOrigin: allowedOrigin } } + +output SERVICE_API_ENDPOINTS array = ['${resources.outputs.apimServiceUrl}/icon-writer-function/IconWriter'] diff --git a/infra/resources.bicep b/infra/resources.bicep index 23b5e86..e56495f 100644 --- a/infra/resources.bicep +++ b/infra/resources.bicep @@ -142,3 +142,15 @@ resource functionAppProperties 'Microsoft.Web/sites/config@2022-03-01' = { apiManagementResources ] } + + +module diagnostics 'app-diagnostics.bicep' = { + name: '${name}-functions-diagnostics' + params: { + appName: functionApp.name + kind: 'functionapp' + diagnosticWorkspaceId: logAnalytics.id + } +} + +output apimServiceUrl string = apiManagementResources.outputs.apimServiceUrl