├── .devcontainer ├── Dockerfile ├── devcontainer.json └── first-run-notice.txt ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── azure.yaml ├── http.sln ├── http ├── .gitignore ├── Program.cs ├── Properties │ ├── launchSettings.json │ ├── serviceDependencies.json │ └── serviceDependencies.local.json ├── host.json ├── http.csproj ├── httpGetFunction.cs ├── httpPostBodyFunction.cs ├── test.http └── testdata.json └── infra ├── abbreviations.json ├── app ├── api.bicep ├── rbac.bicep ├── storage-PrivateEndpoint.bicep └── vnet.bicep ├── main.bicep └── main.parameters.json /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/vscode/devcontainers/universal:latest 2 | 3 | # Copy custom first notice message. 4 | COPY first-run-notice.txt /tmp/staging/ 5 | RUN sudo mv -f /tmp/staging/first-run-notice.txt /usr/local/etc/vscode-dev-containers/ \ 6 | && sudo rm -rf /tmp/staging 7 | 8 | # Install PowerShell 7.x 9 | RUN sudo apt-get update \ 10 | && sudo apt-get install -y wget apt-transport-https software-properties-common \ 11 | && wget -q https://packages.microsoft.com/config/ubuntu/$(. /etc/os-release && echo $VERSION_ID)/packages-microsoft-prod.deb \ 12 | && sudo dpkg -i packages-microsoft-prod.deb \ 13 | && sudo apt-get update \ 14 | && sudo apt-get install -y powershell 15 | 16 | # Install Azure Functions Core Tools 17 | RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg \ 18 | && sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg \ 19 | && sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-$(lsb_release -cs)-prod $(lsb_release -cs) main" > /etc/apt/sources.list.d/dotnetdev.list' \ 20 | && sudo apt-get update \ 21 | && sudo apt-get install -y azure-functions-core-tools-4 22 | 23 | # Install Azure Developer CLI 24 | RUN curl -fsSL https://aka.ms/install-azd.sh | bash 25 | 26 | # Install mechanical-markdown for quickstart validations 27 | RUN pip install mechanical-markdown 28 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Functions Quickstarts Codespace", 3 | "dockerFile": "Dockerfile", 4 | "features": { 5 | "azure-cli": "latest" 6 | }, 7 | "customizations": { 8 | "vscode": { 9 | "extensions": [ 10 | "ms-azuretools.vscode-bicep", 11 | "ms-azuretools.vscode-docker", 12 | "ms-azuretools.vscode-azurefunctions", 13 | "GitHub.copilot", 14 | "humao.rest-client" 15 | ] 16 | } 17 | }, 18 | "mounts": [ 19 | // Mount docker-in-docker library volume 20 | "source=codespaces-linux-var-lib-docker,target=/var/lib/docker,type=volume" 21 | ], 22 | // Always run image-defined docker-init.sh to enable docker-in-docker 23 | "overrideCommand": false, 24 | "remoteUser": "codespace", 25 | "runArgs": [ 26 | // Enable ptrace-based debugging for Go in container 27 | "--cap-add=SYS_PTRACE", 28 | "--security-opt", 29 | "seccomp=unconfined", 30 | 31 | // Enable docker-in-docker configuration 32 | "--init", 33 | "--privileged" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.devcontainer/first-run-notice.txt: -------------------------------------------------------------------------------- 1 | 👋 Welcome to the Functions Codespace! You are on the Functions Quickstarts image. 2 | It includes everything needed to run through our tutorials and quickstart applications. 3 | 4 | 📚 Functions docs can be found at: https://learn.microsoft.com/en-us/azure/azure-functions/ 5 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | ``` 9 | - [ ] bug report -> please search issues before submitting 10 | - [ ] feature request 11 | - [ ] documentation issue or request 12 | - [ ] regression (a behavior that used to work and stopped in a new release) 13 | ``` 14 | 15 | ### Minimal steps to reproduce 16 | > 17 | 18 | ### Any log messages given by the failure 19 | > 20 | 21 | ### Expected/desired behavior 22 | > 23 | 24 | ### OS and Version? 25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 26 | 27 | ### Versions 28 | > 29 | 30 | ### Mention any other details that might be useful 31 | 32 | > --------------------------------------------------------------- 33 | > Thanks! We'll be in touch soon. 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | ``` 8 | [ ] Yes 9 | [ ] No 10 | ``` 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | ``` 17 | [ ] Bugfix 18 | [ ] Feature 19 | [ ] Code style update (formatting, local variables) 20 | [ ] Refactoring (no functional changes, no api changes) 21 | [ ] Documentation content changes 22 | [ ] Other... Please describe: 23 | ``` 24 | 25 | ## How to Test 26 | * Get the code 27 | 28 | ``` 29 | git clone [repo-address] 30 | cd [repo-name] 31 | git checkout [branch-name] 32 | npm install 33 | ``` 34 | 35 | * Test the code 36 | 37 | ``` 38 | ``` 39 | 40 | ## What to Check 41 | Verify that the following are valid 42 | * ... 43 | 44 | ## Other Information 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # Azurite config files 8 | __azurite* 9 | __blobstorage* 10 | __queue* 11 | __table* 12 | 13 | # User-specific files 14 | *.suo 15 | *.user 16 | *.userosscache 17 | *.sln.docstates 18 | 19 | # User-specific files (MonoDevelop/Xamarin Studio) 20 | *.userprefs 21 | 22 | # Build results 23 | [Dd]ebug/ 24 | [Dd]ebugPublic/ 25 | [Rr]elease/ 26 | [Rr]eleases/ 27 | x64/ 28 | x86/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | 34 | # Visual Studio 2015 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # MSTest test Results 40 | [Tt]est[Rr]esult*/ 41 | [Bb]uild[Ll]og.* 42 | 43 | # NUNIT 44 | *.VisualState.xml 45 | TestResult.xml 46 | 47 | # Build Results of an ATL Project 48 | [Dd]ebugPS/ 49 | [Rr]eleasePS/ 50 | dlldata.c 51 | 52 | # DNX 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | 57 | *_i.c 58 | *_p.c 59 | *_i.h 60 | *.ilk 61 | *.meta 62 | *.obj 63 | *.pch 64 | *.pdb 65 | *.pgc 66 | *.pgd 67 | *.rsp 68 | *.sbr 69 | *.tlb 70 | *.tli 71 | *.tlh 72 | *.tmp 73 | *.tmp_proj 74 | *.log 75 | *.vspscc 76 | *.vssscc 77 | .builds 78 | *.pidb 79 | *.svclog 80 | *.scc 81 | 82 | # Chutzpah Test files 83 | _Chutzpah* 84 | 85 | # Visual C++ cache files 86 | ipch/ 87 | *.aps 88 | *.ncb 89 | *.opendb 90 | *.opensdf 91 | *.sdf 92 | *.cachefile 93 | *.VC.db 94 | *.VC.VC.opendb 95 | 96 | # Visual Studio profiler 97 | *.psess 98 | *.vsp 99 | *.vspx 100 | *.sap 101 | 102 | # TFS 2012 Local Workspace 103 | $tf/ 104 | 105 | # Guidance Automation Toolkit 106 | *.gpState 107 | 108 | # ReSharper is a .NET coding add-in 109 | _ReSharper*/ 110 | *.[Rr]e[Ss]harper 111 | *.DotSettings.user 112 | 113 | # JustCode is a .NET coding add-in 114 | .JustCode 115 | 116 | # TeamCity is a build add-in 117 | _TeamCity* 118 | 119 | # DotCover is a Code Coverage Tool 120 | *.dotCover 121 | 122 | # NCrunch 123 | _NCrunch_* 124 | .*crunch*.local.xml 125 | nCrunchTemp_* 126 | 127 | # MightyMoose 128 | *.mm.* 129 | AutoTest.Net/ 130 | 131 | # Web workbench (sass) 132 | .sass-cache/ 133 | 134 | # Installshield output folder 135 | [Ee]xpress/ 136 | 137 | # DocProject is a documentation generator add-in 138 | DocProject/buildhelp/ 139 | DocProject/Help/*.HxT 140 | DocProject/Help/*.HxC 141 | DocProject/Help/*.hhc 142 | DocProject/Help/*.hhk 143 | DocProject/Help/*.hhp 144 | DocProject/Help/Html2 145 | DocProject/Help/html 146 | 147 | # Click-Once directory 148 | publish/ 149 | 150 | # Publish Web Output 151 | *.[Pp]ublish.xml 152 | *.azurePubxml 153 | # TODO: Comment the next line if you want to checkin your web deploy settings 154 | # but database connection strings (with potential passwords) will be unencrypted 155 | #*.pubxml 156 | *.publishproj 157 | 158 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 159 | # checkin your Azure Web App publish settings, but sensitive information contained 160 | # in these scripts will be unencrypted 161 | PublishScripts/ 162 | 163 | # NuGet Packages 164 | *.nupkg 165 | # The packages folder can be ignored because of Package Restore 166 | **/packages/* 167 | # except build/, which is used as an MSBuild target. 168 | !**/packages/build/ 169 | # Uncomment if necessary however generally it will be regenerated when needed 170 | #!**/packages/repositories.config 171 | # NuGet v3's project.json files produces more ignoreable files 172 | *.nuget.props 173 | *.nuget.targets 174 | 175 | # Microsoft Azure Build Output 176 | csx/ 177 | *.build.csdef 178 | 179 | # Microsoft Azure Emulator 180 | ecf/ 181 | rcf/ 182 | 183 | # Windows Store app package directories and files 184 | AppPackages/ 185 | BundleArtifacts/ 186 | Package.StoreAssociation.xml 187 | _pkginfo.txt 188 | 189 | # Visual Studio cache files 190 | # files ending in .cache can be ignored 191 | *.[Cc]ache 192 | # but keep track of directories ending in .cache 193 | !*.[Cc]ache/ 194 | 195 | # Others 196 | ClientBin/ 197 | ~$* 198 | *~ 199 | *.dbmdl 200 | *.dbproj.schemaview 201 | *.jfm 202 | *.pfx 203 | *.publishsettings 204 | node_modules/ 205 | orleans.codegen.cs 206 | 207 | # Since there are multiple workflows, uncomment next line to ignore bower_components 208 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 209 | #bower_components/ 210 | 211 | # RIA/Silverlight projects 212 | Generated_Code/ 213 | 214 | # Backup & report files from converting an old project file 215 | # to a newer Visual Studio version. Backup files are not needed, 216 | # because we have git ;-) 217 | _UpgradeReport_Files/ 218 | Backup*/ 219 | UpgradeLog*.XML 220 | UpgradeLog*.htm 221 | 222 | # SQL Server files 223 | *.mdf 224 | *.ldf 225 | 226 | # Business Intelligence projects 227 | *.rdl.data 228 | *.bim.layout 229 | *.bim_*.settings 230 | 231 | # Microsoft Fakes 232 | FakesAssemblies/ 233 | 234 | # GhostDoc plugin setting file 235 | *.GhostDoc.xml 236 | 237 | # Node.js Tools for Visual Studio 238 | .ntvs_analysis.dat 239 | 240 | # Visual Studio 6 build log 241 | *.plg 242 | 243 | # Visual Studio 6 workspace options file 244 | *.opt 245 | 246 | # Visual Studio LightSwitch build output 247 | **/*.HTMLClient/GeneratedArtifacts 248 | **/*.DesktopClient/GeneratedArtifacts 249 | **/*.DesktopClient/ModelManifest.xml 250 | **/*.Server/GeneratedArtifacts 251 | **/*.Server/ModelManifest.xml 252 | _Pvt_Extensions 253 | 254 | # Paket dependency manager 255 | .paket/paket.exe 256 | paket-files/ 257 | 258 | # FAKE - F# Make 259 | .fake/ 260 | 261 | # JetBrains Rider 262 | .idea/ 263 | *.sln.iml 264 | 265 | # CodeRush 266 | .cr/ 267 | 268 | # Python Tools for Visual Studio (PTVS) 269 | __pycache__/ 270 | *.pyc -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions", 4 | "ms-dotnettools.csharp" 5 | ] 6 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to .NET Functions", 6 | "type": "coreclr", 7 | "request": "attach", 8 | "processId": "${command:azureFunctions.pickProcess}" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azureFunctions.deploySubpath": "http/bin/Release/net8.0/publish", 3 | "azureFunctions.projectLanguage": "C#", 4 | "azureFunctions.projectRuntime": "~4", 5 | "debug.internalConsoleOptions": "neverOpen", 6 | "azureFunctions.preDeployTask": "publish (functions)" 7 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "clean (functions)", 6 | "command": "dotnet", 7 | "args": [ 8 | "clean", 9 | "/property:GenerateFullPaths=true", 10 | "/consoleloggerparameters:NoSummary" 11 | ], 12 | "type": "process", 13 | "problemMatcher": "$msCompile", 14 | "options": { 15 | "cwd": "${workspaceFolder}/http" 16 | } 17 | }, 18 | { 19 | "label": "build (functions)", 20 | "command": "dotnet", 21 | "args": [ 22 | "build", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "type": "process", 27 | "dependsOn": "clean (functions)", 28 | "group": { 29 | "kind": "build", 30 | "isDefault": true 31 | }, 32 | "problemMatcher": "$msCompile", 33 | "options": { 34 | "cwd": "${workspaceFolder}/http" 35 | } 36 | }, 37 | { 38 | "label": "clean release (functions)", 39 | "command": "dotnet", 40 | "args": [ 41 | "clean", 42 | "--configuration", 43 | "Release", 44 | "/property:GenerateFullPaths=true", 45 | "/consoleloggerparameters:NoSummary" 46 | ], 47 | "type": "process", 48 | "problemMatcher": "$msCompile", 49 | "options": { 50 | "cwd": "${workspaceFolder}/http" 51 | } 52 | }, 53 | { 54 | "label": "publish (functions)", 55 | "command": "dotnet", 56 | "args": [ 57 | "publish", 58 | "--configuration", 59 | "Release", 60 | "/property:GenerateFullPaths=true", 61 | "/consoleloggerparameters:NoSummary" 62 | ], 63 | "type": "process", 64 | "dependsOn": "clean release (functions)", 65 | "problemMatcher": "$msCompile", 66 | "options": { 67 | "cwd": "${workspaceFolder}/http" 68 | } 69 | }, 70 | { 71 | "type": "func", 72 | "dependsOn": "build (functions)", 73 | "options": { 74 | "cwd": "${workspaceFolder}/http/bin/Debug/net8.0" 75 | }, 76 | "command": "host start", 77 | "isBackground": true, 78 | "problemMatcher": "$func-dotnet-watch" 79 | } 80 | ] 81 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [project-title] Changelog 2 | 3 | 4 | # x.y.z (yyyy-mm-dd) 5 | 6 | *Features* 7 | * ... 8 | 9 | *Bug Fixes* 10 | * ... 11 | 12 | *Breaking Changes* 13 | * ... 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to [project-title] 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 6 | 7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new]. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase master -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 17 | 18 | # Azure Functions C# HTTP Trigger using Azure Developer CLI 19 | 20 | This template repository contains an HTTP trigger reference sample for functions written in C# (isolated process mode) and deployed to Azure using the Azure Developer CLI (`azd`). The sample uses managed identity and a virtual network to make sure deployment is secure by default. You can opt out of a VNet being used in the sample by setting VNET_ENABLED to false in the parameters. 21 | 22 | This source code supports the article [Quickstart: Create and deploy functions to Azure Functions using the Azure Developer CLI](https://learn.microsoft.com/azure/azure-functions/create-first-function-azure-developer-cli?pivots=programming-language-dotnet). 23 | 24 | This project is designed to run on your local computer. You can also use GitHub Codespaces: 25 | 26 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=836901178) 27 | 28 | This codespace is already configured with the required tools to complete this tutorial using either `azd` or Visual Studio Code. If you're working a codespace, skip down to [Prepare your local environment](#prepare-your-local-environment). 29 | 30 | ## Prerequisites 31 | 32 | + [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) 33 | + [Azure Functions Core Tools](https://learn.microsoft.com/azure/azure-functions/functions-run-local?pivots=programming-language-csharp#install-the-azure-functions-core-tools) 34 | + To use Visual Studio to run and debug locally: 35 | + [Visual Studio 2022](https://visualstudio.microsoft.com/vs/). 36 | + Make sure to select the **Azure development** workload during installation. 37 | + To use Visual Studio Code to run and debug locally: 38 | + [Visual Studio Code](https://code.visualstudio.com/) 39 | + [Azure Functions extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions) 40 | 41 | ## Initialize the local project 42 | 43 | You can initialize a project from this `azd` template in one of these ways: 44 | 45 | + Use this `azd init` command from an empty local (root) folder: 46 | 47 | ```shell 48 | azd init --template functions-quickstart-dotnet-azd 49 | ``` 50 | 51 | Supply an environment name, such as `flexquickstart` when prompted. In `azd`, the environment is used to maintain a unique deployment context for your app. 52 | 53 | + Clone the GitHub template repository locally using the `git clone` command: 54 | 55 | ```shell 56 | git clone https://github.com/Azure-Samples/functions-quickstart-dotnet-azd.git 57 | cd functions-quickstart-dotnet-azd 58 | ``` 59 | 60 | You can also clone the repository from your own fork in GitHub. 61 | 62 | ## Prepare your local environment 63 | 64 | Navigate to the `http` app folder and create a file in that folder named _local.settings.json_ that contains this JSON data: 65 | 66 | ```json 67 | { 68 | "IsEncrypted": false, 69 | "Values": { 70 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 71 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated" 72 | } 73 | } 74 | ``` 75 | 76 | ## Run your app from the terminal 77 | 78 | 1. From the `http` folder, run this command to start the Functions host locally: 79 | 80 | ```shell 81 | func start 82 | ``` 83 | 84 | 1. From your HTTP test tool in a new terminal (or from your browser), call the HTTP GET endpoint: 85 | 86 | 1. Test the HTTP POST trigger with a payload using your favorite secure HTTP test tool. 87 | 88 | **Cmd\bash** 89 | 90 | This example runs from the `http` folder and uses the `curl` tool with payload data from the [`testdata.json`](./http/testdata.json) project file: 91 | 92 | ```shell 93 | curl -i http://localhost:7071/api/httppost -H "Content-Type: text/json" -d @testdata.json 94 | ``` 95 | 96 | **PowerShell** 97 | 98 | You can also use this `Invoke-RestMethod` cmdlet in PowerShell from the `http` folder: 99 | 100 | ```powershell 101 | Invoke-RestMethod -Uri http://localhost:7071/api/httppost -Method Post -ContentType "application/json" -InFile "testdata.json" 102 | ``` 103 | 104 | 1. When you're done, press Ctrl+C in the terminal window to stop the `func.exe` host process. 105 | 106 | ## Run your app using Visual Studio Code 107 | 108 | 1. Open the `http` app folder in a new terminal. 109 | 1. Run the `code .` code command to open the project in Visual Studio Code. 110 | 1. In the command palette (F1), type `Azurite: Start`, which enables debugging without warnings. 111 | 1. Press **Run/Debug (F5)** to run in the debugger. Select **Debug anyway** if prompted about local emulator not running. 112 | 1. Send GET and POST requests to the `httpget` and `httppost` endpoints respectively using your HTTP test tool (or browser for `httpget`). If you have the [RestClient](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension installed, you can execute requests directly from the [`test.http`](./http/test.http) project file. 113 | 114 | ## Run your app using Visual Studio 115 | 116 | 1. Open the `http.sln` solution file in Visual Studio. 117 | 1. Press **Run/F5** to run in the debugger. Make a note of the `localhost` URL endpoints, including the port, which might not be `7071`. 118 | 1. Open the [`test.http`](./http/test.http) project file, update the port on the `localhost` URL (if needed), and then use the built-in HTTP client to call the `httpget` and `httppost` endpoints. 119 | 120 | ## Source Code 121 | 122 | The function code for the `httpget` and `httppost` endpoints are defined in [`httpGetFunction.cs`](./http/httpGetFunction.cs) and [`httpPostBodyFunction.cs`](./http/httpPostBodyFunction.cs), respectively. The `Function` attribute applied to the async `Run` method sets the name of the function endpoint. 123 | 124 | This code shows an HTTP GET (webhook): 125 | 126 | ```csharp 127 | [Function("httpget")] 128 | public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get")] 129 | HttpRequest req, 130 | string name) 131 | { 132 | var returnValue = string.IsNullOrEmpty(name) 133 | ? "Hello, World." 134 | : $"Hello, {name}."; 135 | 136 | _logger.LogInformation($"C# HTTP trigger function processed a request for {returnValue}."); 137 | 138 | return new OkObjectResult(returnValue); 139 | } 140 | ``` 141 | 142 | This code shows the HTTP POST that received a JSON formatted `person` object in the request body and returns a message using the values in the payload: 143 | 144 | ```csharp 145 | [Function("httppost")] 146 | public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req, 147 | [FromBody] Person person) 148 | { 149 | _logger.LogInformation($"C# HTTP POST trigger function processed a request for url {req.Body}"); 150 | 151 | if (string.IsNullOrEmpty(person.Name) | string.IsNullOrEmpty(person.Age.ToString()) | person.Age == 0) 152 | { 153 | _logger.LogInformation("C# HTTP POST trigger function processed a request with no name/age provided."); 154 | return new BadRequestObjectResult("Please provide both name and age in the request body."); 155 | } 156 | 157 | var returnValue = $"Hello, {person.Name}! You are {person.Age} years old."; 158 | 159 | _logger.LogInformation($"C# HTTP POST trigger function processed a request for {person.Name} who is {person.Age} years old."); 160 | return new OkObjectResult(returnValue); 161 | } 162 | ``` 163 | 164 | ## Deploy to Azure 165 | 166 | Run this command to provision the function app, with any required Azure resources, and deploy your code: 167 | 168 | ```shell 169 | azd up 170 | ``` 171 | 172 | By default, this sample prompts to enable a virtual network for enhanced security. If you want to deploy without a virtual network without prompting, you can configure `VNET_ENABLED` to `false` before running `azd up`: 173 | 174 | ```bash 175 | azd env set VNET_ENABLED false 176 | azd up 177 | ``` 178 | 179 | You're prompted to supply these required deployment parameters: 180 | 181 | | Parameter | Description | 182 | | ---- | ---- | 183 | | _Environment name_ | An environment that's used to maintain a unique deployment context for your app. You won't be prompted if you created the local project using `azd init`.| 184 | | _Azure subscription_ | Subscription in which your resources are created.| 185 | | _Azure location_ | Azure region in which to create the resource group that contains the new Azure resources. Only regions that currently support the Flex Consumption plan are shown.| 186 | 187 | After publish completes successfully, `azd` provides you with the URL endpoints of your new functions, but without the function key values required to access the endpoints. To learn how to obtain these same endpoints along with the required function keys, see [Invoke the function on Azure](https://learn.microsoft.com/azure/azure-functions/create-first-function-azure-developer-cli?pivots=programming-language-dotnet#invoke-the-function-on-azure) in the companion article [Quickstart: Create and deploy functions to Azure Functions using the Azure Developer CLI](https://learn.microsoft.com/azure/azure-functions/create-first-function-azure-developer-cli?pivots=programming-language-dotnet). 188 | 189 | ## Redeploy your code 190 | 191 | You can run the `azd up` command as many times as you need to both provision your Azure resources and deploy code updates to your function app. 192 | 193 | >[!NOTE] 194 | >Deployed code files are always overwritten by the latest deployment package. 195 | 196 | ## Clean up resources 197 | 198 | When you're done working with your function app and related resources, you can use this command to delete the function app and its related resources from Azure and avoid incurring any further costs: 199 | 200 | ```shell 201 | azd down 202 | ``` 203 | -------------------------------------------------------------------------------- /azure.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json 2 | 3 | name: starter-dotnet8-flex-func 4 | metadata: 5 | template: functions-quickstart-dotnet-azd@1.1.0 6 | services: 7 | api: 8 | project: ./http/ 9 | language: dotnet 10 | host: function 11 | -------------------------------------------------------------------------------- /http.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.10.35027.167 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "http", "http\http.csproj", "{32C6DAE7-2329-47AB-8551-2A9EF0353C9C}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {32C6DAE7-2329-47AB-8551-2A9EF0353C9C}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {F5C50B53-B73E-4C2E-AF58-F034B65DEFAD} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /http/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | -------------------------------------------------------------------------------- /http/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Functions.Worker; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | var host = new HostBuilder() 6 | .ConfigureFunctionsWebApplication() 7 | .ConfigureServices(services => 8 | { 9 | services.AddApplicationInsightsTelemetryWorkerService(); 10 | services.ConfigureFunctionsApplicationInsights(); 11 | }) 12 | .Build(); 13 | 14 | host.Run(); 15 | -------------------------------------------------------------------------------- /http/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "FunctionHttp": { 4 | "commandName": "Project", 5 | "commandLineArgs": "--port 7129", 6 | "launchBrowser": false 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /http/Properties/serviceDependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "appInsights1": { 4 | "type": "appInsights" 5 | }, 6 | "storage1": { 7 | "type": "storage", 8 | "connectionId": "AzureWebJobsStorage" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /http/Properties/serviceDependencies.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "appInsights1": { 4 | "type": "appInsights.sdk" 5 | }, 6 | "storage1": { 7 | "type": "storage.emulator", 8 | "connectionId": "AzureWebJobsStorage" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /http/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | }, 9 | "enableLiveMetricsFilters": true 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /http/http.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | v4 5 | Exe 6 | enable 7 | enable 8 | 09bd123b-3401-4507-b92c-0b283b95b537 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | PreserveNewest 25 | Never 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /http/httpGetFunction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Azure.Functions.Worker; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Company.Function 7 | { 8 | public class httpGetFunction 9 | { 10 | private readonly ILogger _logger; 11 | 12 | public httpGetFunction(ILoggerFactory loggerFactory) 13 | { 14 | _logger = loggerFactory.CreateLogger(); 15 | } 16 | 17 | [Function("httpget")] 18 | public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get")] 19 | HttpRequest req, 20 | string name) 21 | { 22 | var returnValue = string.IsNullOrEmpty(name) 23 | ? "Hello, World." 24 | : $"Hello, {name}."; 25 | 26 | _logger.LogInformation($"C# HTTP trigger function processed a request for {returnValue}."); 27 | 28 | return new OkObjectResult(returnValue); 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /http/httpPostBodyFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Azure.Functions.Worker; 5 | using Microsoft.Extensions.Logging; 6 | using FromBodyAttribute = Microsoft.Azure.Functions.Worker.Http.FromBodyAttribute; 7 | 8 | namespace Company.Function 9 | { 10 | public class HttpPostBody 11 | { 12 | private readonly ILogger _logger; 13 | 14 | public HttpPostBody(ILoggerFactory loggerFactory) 15 | { 16 | _logger = loggerFactory.CreateLogger(); 17 | } 18 | 19 | [Function("httppost")] 20 | public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req, 21 | [FromBody] Person person) 22 | { 23 | _logger.LogInformation($"C# HTTP POST trigger function processed a request for url {req.Body}"); 24 | 25 | if (string.IsNullOrEmpty(person.Name) | string.IsNullOrEmpty(person.Age.ToString()) | person.Age == 0) 26 | { 27 | _logger.LogInformation("C# HTTP POST trigger function processed a request with no name/age provided."); 28 | return new BadRequestObjectResult("Please provide both name and age in the request body."); 29 | } 30 | 31 | var returnValue = $"Hello, {person.Name}! You are {person.Age} years old."; 32 | 33 | _logger.LogInformation($"C# HTTP POST trigger function processed a request for {person.Name} who is {person.Age} years old."); 34 | return new OkObjectResult(returnValue); 35 | } 36 | } 37 | public record Person([property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("age")] int Age); 38 | } -------------------------------------------------------------------------------- /http/test.http: -------------------------------------------------------------------------------- 1 | # Tip: You might need to modify the port value on the 2 | # localhost URL when debugging in Visual Studio. 3 | 4 | GET http://localhost:7071/api/httpget HTTP/1.1 5 | 6 | ### 7 | 8 | GET http://localhost:7071/api/httpget?name=World HTTP/1.1 9 | 10 | ### 11 | 12 | POST http://localhost:7071/api/httppost HTTP/1.1 13 | content-type: application/json 14 | 15 | { 16 | "name": "Awesome Developer", 17 | "age": 25 18 | } 19 | -------------------------------------------------------------------------------- /http/testdata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Awesome Developer", 3 | "age": 25 4 | } -------------------------------------------------------------------------------- /infra/abbreviations.json: -------------------------------------------------------------------------------- 1 | { 2 | "analysisServicesServers": "as", 3 | "apiManagementService": "apim-", 4 | "appConfigurationConfigurationStores": "appcs-", 5 | "appManagedEnvironments": "cae-", 6 | "appContainerApps": "ca-", 7 | "authorizationPolicyDefinitions": "policy-", 8 | "automationAutomationAccounts": "aa-", 9 | "blueprintBlueprints": "bp-", 10 | "blueprintBlueprintsArtifacts": "bpa-", 11 | "cacheRedis": "redis-", 12 | "cdnProfiles": "cdnp-", 13 | "cdnProfilesEndpoints": "cdne-", 14 | "cognitiveServicesAccounts": "cog-", 15 | "cognitiveServicesFormRecognizer": "cog-fr-", 16 | "cognitiveServicesTextAnalytics": "cog-ta-", 17 | "computeAvailabilitySets": "avail-", 18 | "computeCloudServices": "cld-", 19 | "computeDiskEncryptionSets": "des", 20 | "computeDisks": "disk", 21 | "computeDisksOs": "osdisk", 22 | "computeGalleries": "gal", 23 | "computeSnapshots": "snap-", 24 | "computeVirtualMachines": "vm", 25 | "computeVirtualMachineScaleSets": "vmss-", 26 | "containerInstanceContainerGroups": "ci", 27 | "containerRegistryRegistries": "cr", 28 | "containerServiceManagedClusters": "aks-", 29 | "databricksWorkspaces": "dbw-", 30 | "dataFactoryFactories": "adf-", 31 | "dataLakeAnalyticsAccounts": "dla", 32 | "dataLakeStoreAccounts": "dls", 33 | "dataMigrationServices": "dms-", 34 | "dBforMySQLServers": "mysql-", 35 | "dBforPostgreSQLServers": "psql-", 36 | "devicesIotHubs": "iot-", 37 | "devicesProvisioningServices": "provs-", 38 | "devicesProvisioningServicesCertificates": "pcert-", 39 | "documentDBDatabaseAccounts": "cosmos-", 40 | "eventGridDomains": "evgd-", 41 | "eventGridDomainsTopics": "evgt-", 42 | "eventGridEventSubscriptions": "evgs-", 43 | "eventHubNamespaces": "evhns-", 44 | "eventHubNamespacesEventHubs": "evh-", 45 | "hdInsightClustersHadoop": "hadoop-", 46 | "hdInsightClustersHbase": "hbase-", 47 | "hdInsightClustersKafka": "kafka-", 48 | "hdInsightClustersMl": "mls-", 49 | "hdInsightClustersSpark": "spark-", 50 | "hdInsightClustersStorm": "storm-", 51 | "hybridComputeMachines": "arcs-", 52 | "insightsActionGroups": "ag-", 53 | "insightsComponents": "appi-", 54 | "keyVaultVaults": "kv-", 55 | "kubernetesConnectedClusters": "arck", 56 | "kustoClusters": "dec", 57 | "kustoClustersDatabases": "dedb", 58 | "logicIntegrationAccounts": "ia-", 59 | "logicWorkflows": "logic-", 60 | "machineLearningServicesWorkspaces": "mlw-", 61 | "managedIdentityUserAssignedIdentities": "id-", 62 | "managementManagementGroups": "mg-", 63 | "migrateAssessmentProjects": "migr-", 64 | "networkApplicationGateways": "agw-", 65 | "networkApplicationSecurityGroups": "asg-", 66 | "networkAzureFirewalls": "afw-", 67 | "networkBastionHosts": "bas-", 68 | "networkConnections": "con-", 69 | "networkDnsZones": "dnsz-", 70 | "networkExpressRouteCircuits": "erc-", 71 | "networkFirewallPolicies": "afwp-", 72 | "networkFirewallPoliciesWebApplication": "waf", 73 | "networkFirewallPoliciesRuleGroups": "wafrg", 74 | "networkFrontDoors": "fd-", 75 | "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", 76 | "networkLoadBalancersExternal": "lbe-", 77 | "networkLoadBalancersInternal": "lbi-", 78 | "networkLoadBalancersInboundNatRules": "rule-", 79 | "networkLocalNetworkGateways": "lgw-", 80 | "networkNatGateways": "ng-", 81 | "networkNetworkInterfaces": "nic-", 82 | "networkNetworkSecurityGroups": "nsg-", 83 | "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", 84 | "networkNetworkWatchers": "nw-", 85 | "networkPrivateDnsZones": "pdnsz-", 86 | "networkPrivateLinkServices": "pl-", 87 | "networkPublicIPAddresses": "pip-", 88 | "networkPublicIPPrefixes": "ippre-", 89 | "networkRouteFilters": "rf-", 90 | "networkRouteTables": "rt-", 91 | "networkRouteTablesRoutes": "udr-", 92 | "networkTrafficManagerProfiles": "traf-", 93 | "networkVirtualNetworkGateways": "vgw-", 94 | "networkVirtualNetworks": "vnet-", 95 | "networkVirtualNetworksSubnets": "snet-", 96 | "networkVirtualNetworksVirtualNetworkPeerings": "peer-", 97 | "networkVirtualWans": "vwan-", 98 | "networkVpnGateways": "vpng-", 99 | "networkVpnGatewaysVpnConnections": "vcn-", 100 | "networkVpnGatewaysVpnSites": "vst-", 101 | "notificationHubsNamespaces": "ntfns-", 102 | "notificationHubsNamespacesNotificationHubs": "ntf-", 103 | "operationalInsightsWorkspaces": "log-", 104 | "portalDashboards": "dash-", 105 | "powerBIDedicatedCapacities": "pbi-", 106 | "purviewAccounts": "pview-", 107 | "recoveryServicesVaults": "rsv-", 108 | "resourcesResourceGroups": "rg-", 109 | "searchSearchServices": "srch-", 110 | "serviceBusNamespaces": "sb-", 111 | "serviceBusNamespacesQueues": "sbq-", 112 | "serviceBusNamespacesTopics": "sbt-", 113 | "serviceEndPointPolicies": "se-", 114 | "serviceFabricClusters": "sf-", 115 | "signalRServiceSignalR": "sigr", 116 | "sqlManagedInstances": "sqlmi-", 117 | "sqlServers": "sql-", 118 | "sqlServersDataWarehouse": "sqldw-", 119 | "sqlServersDatabases": "sqldb-", 120 | "sqlServersDatabasesStretch": "sqlstrdb-", 121 | "storageStorageAccounts": "st", 122 | "storageStorageAccountsVm": "stvm", 123 | "storSimpleManagers": "ssimp", 124 | "streamAnalyticsCluster": "asa-", 125 | "synapseWorkspaces": "syn", 126 | "synapseWorkspacesAnalyticsWorkspaces": "synw", 127 | "synapseWorkspacesSqlPoolsDedicated": "syndp", 128 | "synapseWorkspacesSqlPoolsSpark": "synsp", 129 | "timeSeriesInsightsEnvironments": "tsi-", 130 | "webServerFarms": "plan-", 131 | "webSitesAppService": "app-", 132 | "webSitesAppServiceEnvironment": "ase-", 133 | "webSitesFunctions": "func-", 134 | "webStaticSites": "stapp-" 135 | } -------------------------------------------------------------------------------- /infra/app/api.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | @description('Primary location for all resources & Flex Consumption Function App') 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | param applicationInsightsName string = '' 6 | param appServicePlanId string 7 | param appSettings object = {} 8 | param runtimeName string 9 | param runtimeVersion string 10 | param serviceName string = 'api' 11 | param storageAccountName string 12 | param deploymentStorageContainerName string 13 | param virtualNetworkSubnetId string = '' 14 | param instanceMemoryMB int = 2048 15 | param maximumInstanceCount int = 100 16 | param identityId string = '' 17 | param identityClientId string = '' 18 | param enableBlob bool = true 19 | param enableQueue bool = false 20 | param enableTable bool = false 21 | param enableFile bool = false 22 | 23 | @allowed(['SystemAssigned', 'UserAssigned']) 24 | param identityType string = 'UserAssigned' 25 | 26 | var applicationInsightsIdentity = 'ClientId=${identityClientId};Authorization=AAD' 27 | var kind = 'functionapp,linux' 28 | 29 | // Create base application settings 30 | var baseAppSettings = { 31 | // Only include required credential settings unconditionally 32 | AzureWebJobsStorage__credential: 'managedidentity' 33 | AzureWebJobsStorage__clientId: identityClientId 34 | 35 | // Application Insights settings are always included 36 | APPLICATIONINSIGHTS_AUTHENTICATION_STRING: applicationInsightsIdentity 37 | APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString 38 | } 39 | 40 | // Dynamically build storage endpoint settings based on feature flags 41 | var blobSettings = enableBlob ? { AzureWebJobsStorage__blobServiceUri: stg.properties.primaryEndpoints.blob } : {} 42 | var queueSettings = enableQueue ? { AzureWebJobsStorage__queueServiceUri: stg.properties.primaryEndpoints.queue } : {} 43 | var tableSettings = enableTable ? { AzureWebJobsStorage__tableServiceUri: stg.properties.primaryEndpoints.table } : {} 44 | var fileSettings = enableFile ? { AzureWebJobsStorage__fileServiceUri: stg.properties.primaryEndpoints.file } : {} 45 | 46 | // Merge all app settings 47 | var allAppSettings = union( 48 | appSettings, 49 | blobSettings, 50 | queueSettings, 51 | tableSettings, 52 | fileSettings, 53 | baseAppSettings 54 | ) 55 | 56 | resource stg 'Microsoft.Storage/storageAccounts@2022-09-01' existing = { 57 | name: storageAccountName 58 | } 59 | 60 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { 61 | name: applicationInsightsName 62 | } 63 | 64 | // Create a Flex Consumption Function App to host the API 65 | module api 'br/public:avm/res/web/site:0.15.1' = { 66 | name: '${serviceName}-flex-consumption' 67 | params: { 68 | kind: kind 69 | name: name 70 | location: location 71 | tags: union(tags, { 'azd-service-name': serviceName }) 72 | serverFarmResourceId: appServicePlanId 73 | managedIdentities: { 74 | systemAssigned: identityType == 'SystemAssigned' 75 | userAssignedResourceIds: [ 76 | '${identityId}' 77 | ] 78 | } 79 | functionAppConfig: { 80 | deployment: { 81 | storage: { 82 | type: 'blobContainer' 83 | value: '${stg.properties.primaryEndpoints.blob}${deploymentStorageContainerName}' 84 | authentication: { 85 | type: identityType == 'SystemAssigned' ? 'SystemAssignedIdentity' : 'UserAssignedIdentity' 86 | userAssignedIdentityResourceId: identityType == 'UserAssigned' ? identityId : '' 87 | } 88 | } 89 | } 90 | scaleAndConcurrency: { 91 | instanceMemoryMB: instanceMemoryMB 92 | maximumInstanceCount: maximumInstanceCount 93 | } 94 | runtime: { 95 | name: runtimeName 96 | version: runtimeVersion 97 | } 98 | } 99 | siteConfig: { 100 | alwaysOn: false 101 | } 102 | virtualNetworkSubnetId: !empty(virtualNetworkSubnetId) ? virtualNetworkSubnetId : null 103 | appSettingsKeyValuePairs: allAppSettings 104 | } 105 | } 106 | 107 | output SERVICE_API_NAME string = api.outputs.name 108 | // Ensure output is always string, handle potential null from module output if SystemAssigned is not used 109 | output SERVICE_API_IDENTITY_PRINCIPAL_ID string = identityType == 'SystemAssigned' ? api.outputs.?systemAssignedMIPrincipalId ?? '' : '' 110 | -------------------------------------------------------------------------------- /infra/app/rbac.bicep: -------------------------------------------------------------------------------- 1 | param storageAccountName string 2 | param appInsightsName string 3 | param managedIdentityPrincipalId string // Principal ID for the Managed Identity 4 | param userIdentityPrincipalId string = '' // Principal ID for the User Identity 5 | param allowUserIdentityPrincipal bool = false // Flag to enable user identity role assignments 6 | param enableBlob bool = true 7 | param enableQueue bool = false 8 | param enableTable bool = false 9 | 10 | // Define Role Definition IDs internally 11 | var storageRoleDefinitionId = 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b' //Storage Blob Data Owner role 12 | var queueRoleDefinitionId = '974c5e8b-45b9-4653-ba55-5f855dd0fb88' // Storage Queue Data Contributor role 13 | var tableRoleDefinitionId = '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3' // Storage Table Data Contributor role 14 | var monitoringRoleDefinitionId = '3913510d-42f4-4e42-8a64-420c390055eb' // Monitoring Metrics Publisher role ID 15 | 16 | resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' existing = { 17 | name: storageAccountName 18 | } 19 | 20 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { 21 | name: appInsightsName 22 | } 23 | 24 | // Role assignment for Storage Account (Blob) - Managed Identity 25 | resource storageRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableBlob) { 26 | name: guid(storageAccount.id, managedIdentityPrincipalId, storageRoleDefinitionId) // Use managed identity ID 27 | scope: storageAccount 28 | properties: { 29 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', storageRoleDefinitionId) 30 | principalId: managedIdentityPrincipalId // Use managed identity ID 31 | principalType: 'ServicePrincipal' // Managed Identity is a Service Principal 32 | } 33 | } 34 | 35 | // Role assignment for Storage Account (Blob) - User Identity 36 | resource storageRoleAssignment_User 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableBlob && allowUserIdentityPrincipal && !empty(userIdentityPrincipalId)) { 37 | name: guid(storageAccount.id, userIdentityPrincipalId, storageRoleDefinitionId) 38 | scope: storageAccount 39 | properties: { 40 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', storageRoleDefinitionId) 41 | principalId: userIdentityPrincipalId // Use user identity ID 42 | principalType: 'User' // User Identity is a User Principal 43 | } 44 | } 45 | 46 | // Role assignment for Storage Account (Queue) - Managed Identity 47 | resource queueRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableQueue) { 48 | name: guid(storageAccount.id, managedIdentityPrincipalId, queueRoleDefinitionId) // Use managed identity ID 49 | scope: storageAccount 50 | properties: { 51 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', queueRoleDefinitionId) 52 | principalId: managedIdentityPrincipalId // Use managed identity ID 53 | principalType: 'ServicePrincipal' // Managed Identity is a Service Principal 54 | } 55 | } 56 | 57 | // Role assignment for Storage Account (Queue) - User Identity 58 | resource queueRoleAssignment_User 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableQueue && allowUserIdentityPrincipal && !empty(userIdentityPrincipalId)) { 59 | name: guid(storageAccount.id, userIdentityPrincipalId, queueRoleDefinitionId) 60 | scope: storageAccount 61 | properties: { 62 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', queueRoleDefinitionId) 63 | principalId: userIdentityPrincipalId // Use user identity ID 64 | principalType: 'User' // User Identity is a User Principal 65 | } 66 | } 67 | 68 | // Role assignment for Storage Account (Table) - Managed Identity 69 | resource tableRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableTable) { 70 | name: guid(storageAccount.id, managedIdentityPrincipalId, tableRoleDefinitionId) // Use managed identity ID 71 | scope: storageAccount 72 | properties: { 73 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', tableRoleDefinitionId) 74 | principalId: managedIdentityPrincipalId // Use managed identity ID 75 | principalType: 'ServicePrincipal' // Managed Identity is a Service Principal 76 | } 77 | } 78 | 79 | // Role assignment for Storage Account (Table) - User Identity 80 | resource tableRoleAssignment_User 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableTable && allowUserIdentityPrincipal && !empty(userIdentityPrincipalId)) { 81 | name: guid(storageAccount.id, userIdentityPrincipalId, tableRoleDefinitionId) 82 | scope: storageAccount 83 | properties: { 84 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', tableRoleDefinitionId) 85 | principalId: userIdentityPrincipalId // Use user identity ID 86 | principalType: 'User' // User Identity is a User Principal 87 | } 88 | } 89 | 90 | // Role assignment for Application Insights - Managed Identity 91 | resource appInsightsRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 92 | name: guid(applicationInsights.id, managedIdentityPrincipalId, monitoringRoleDefinitionId) // Use managed identity ID 93 | scope: applicationInsights 94 | properties: { 95 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', monitoringRoleDefinitionId) 96 | principalId: managedIdentityPrincipalId // Use managed identity ID 97 | principalType: 'ServicePrincipal' // Managed Identity is a Service Principal 98 | } 99 | } 100 | 101 | // Role assignment for Application Insights - User Identity 102 | resource appInsightsRoleAssignment_User 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (allowUserIdentityPrincipal && !empty(userIdentityPrincipalId)) { 103 | name: guid(applicationInsights.id, userIdentityPrincipalId, monitoringRoleDefinitionId) 104 | scope: applicationInsights 105 | properties: { 106 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', monitoringRoleDefinitionId) 107 | principalId: userIdentityPrincipalId // Use user identity ID 108 | principalType: 'User' // User Identity is a User Principal 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /infra/app/storage-PrivateEndpoint.bicep: -------------------------------------------------------------------------------- 1 | param virtualNetworkName string 2 | param subnetName string 3 | @description('Specifies the storage account resource name') 4 | param resourceName string 5 | param location string = resourceGroup().location 6 | param tags object = {} 7 | param enableBlob bool = true 8 | param enableQueue bool = false 9 | param enableTable bool = false 10 | 11 | resource vnet 'Microsoft.Network/virtualNetworks@2021-08-01' existing = { 12 | name: virtualNetworkName 13 | } 14 | 15 | resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { 16 | name: resourceName 17 | } 18 | 19 | // Storage DNS zone names 20 | var blobPrivateDNSZoneName = 'privatelink.blob.${environment().suffixes.storage}' 21 | var queuePrivateDNSZoneName = 'privatelink.queue.${environment().suffixes.storage}' 22 | var tablePrivateDNSZoneName = 'privatelink.table.${environment().suffixes.storage}' 23 | 24 | // AVM module for Blob Private Endpoint with private DNS zone 25 | module blobPrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.11.0' = if (enableBlob) { 26 | name: 'blob-private-endpoint-deployment' 27 | params: { 28 | name: 'blob-private-endpoint' 29 | location: location 30 | tags: tags 31 | subnetResourceId: '${vnet.id}/subnets/${subnetName}' 32 | privateLinkServiceConnections: [ 33 | { 34 | name: 'blobPrivateLinkConnection' 35 | properties: { 36 | privateLinkServiceId: storageAccount.id 37 | groupIds: [ 38 | 'blob' 39 | ] 40 | } 41 | } 42 | ] 43 | customDnsConfigs: [] 44 | // Creates private DNS zone and links 45 | privateDnsZoneGroup: { 46 | name: 'blobPrivateDnsZoneGroup' 47 | privateDnsZoneGroupConfigs: [ 48 | { 49 | name: 'storageBlobARecord' 50 | privateDnsZoneResourceId: enableBlob ? privateDnsZoneBlobDeployment.outputs.resourceId : '' 51 | } 52 | ] 53 | } 54 | } 55 | } 56 | 57 | // AVM module for Queue Private Endpoint with private DNS zone 58 | module queuePrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.11.0' = if (enableQueue) { 59 | name: 'queue-private-endpoint-deployment' 60 | params: { 61 | name: 'queue-private-endpoint' 62 | location: location 63 | tags: tags 64 | subnetResourceId: '${vnet.id}/subnets/${subnetName}' 65 | privateLinkServiceConnections: [ 66 | { 67 | name: 'queuePrivateLinkConnection' 68 | properties: { 69 | privateLinkServiceId: storageAccount.id 70 | groupIds: [ 71 | 'queue' 72 | ] 73 | } 74 | } 75 | ] 76 | customDnsConfigs: [] 77 | // Creates private DNS zone and links 78 | privateDnsZoneGroup: { 79 | name: 'queuePrivateDnsZoneGroup' 80 | privateDnsZoneGroupConfigs: [ 81 | { 82 | name: 'storageQueueARecord' 83 | privateDnsZoneResourceId: enableQueue ? privateDnsZoneQueueDeployment.outputs.resourceId : '' 84 | } 85 | ] 86 | } 87 | } 88 | } 89 | 90 | // AVM module for Table Private Endpoint with private DNS zone 91 | module tablePrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.11.0' = if (enableTable) { 92 | name: 'table-private-endpoint-deployment' 93 | params: { 94 | name: 'table-private-endpoint' 95 | location: location 96 | tags: tags 97 | subnetResourceId: '${vnet.id}/subnets/${subnetName}' 98 | privateLinkServiceConnections: [ 99 | { 100 | name: 'tablePrivateLinkConnection' 101 | properties: { 102 | privateLinkServiceId: storageAccount.id 103 | groupIds: [ 104 | 'table' 105 | ] 106 | } 107 | } 108 | ] 109 | customDnsConfigs: [] 110 | // Creates private DNS zone and links 111 | privateDnsZoneGroup: { 112 | name: 'tablePrivateDnsZoneGroup' 113 | privateDnsZoneGroupConfigs: [ 114 | { 115 | name: 'storageTableARecord' 116 | privateDnsZoneResourceId: enableTable ? privateDnsZoneTableDeployment.outputs.resourceId : '' 117 | } 118 | ] 119 | } 120 | } 121 | } 122 | 123 | // AVM module for Blob Private DNS Zone 124 | module privateDnsZoneBlobDeployment 'br/public:avm/res/network/private-dns-zone:0.7.1' = if (enableBlob) { 125 | name: 'blob-private-dns-zone-deployment' 126 | params: { 127 | name: blobPrivateDNSZoneName 128 | location: 'global' 129 | tags: tags 130 | virtualNetworkLinks: [ 131 | { 132 | name: '${resourceName}-blob-link-${take(toLower(uniqueString(resourceName, virtualNetworkName)), 4)}' 133 | virtualNetworkResourceId: vnet.id 134 | registrationEnabled: false 135 | location: 'global' 136 | tags: tags 137 | } 138 | ] 139 | } 140 | } 141 | 142 | // AVM module for Queue Private DNS Zone 143 | module privateDnsZoneQueueDeployment 'br/public:avm/res/network/private-dns-zone:0.7.1' = if (enableQueue) { 144 | name: 'queue-private-dns-zone-deployment' 145 | params: { 146 | name: queuePrivateDNSZoneName 147 | location: 'global' 148 | tags: tags 149 | virtualNetworkLinks: [ 150 | { 151 | name: '${resourceName}-queue-link-${take(toLower(uniqueString(resourceName, virtualNetworkName)), 4)}' 152 | virtualNetworkResourceId: vnet.id 153 | registrationEnabled: false 154 | location: 'global' 155 | tags: tags 156 | } 157 | ] 158 | } 159 | } 160 | 161 | // AVM module for Table Private DNS Zone 162 | module privateDnsZoneTableDeployment 'br/public:avm/res/network/private-dns-zone:0.7.1' = if (enableTable) { 163 | name: 'table-private-dns-zone-deployment' 164 | params: { 165 | name: tablePrivateDNSZoneName 166 | location: 'global' 167 | tags: tags 168 | virtualNetworkLinks: [ 169 | { 170 | name: '${resourceName}-table-link-${take(toLower(uniqueString(resourceName, virtualNetworkName)), 4)}' 171 | virtualNetworkResourceId: vnet.id 172 | registrationEnabled: false 173 | location: 'global' 174 | tags: tags 175 | } 176 | ] 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /infra/app/vnet.bicep: -------------------------------------------------------------------------------- 1 | @description('Specifies the name of the virtual network.') 2 | param vNetName string 3 | 4 | @description('Specifies the location.') 5 | param location string = resourceGroup().location 6 | 7 | @description('Specifies the name of the subnet for the Service Bus private endpoint.') 8 | param peSubnetName string = 'private-endpoints-subnet' 9 | 10 | @description('Specifies the name of the subnet for Function App virtual network integration.') 11 | param appSubnetName string = 'app' 12 | 13 | param tags object = {} 14 | 15 | // Migrated to use AVM module instead of direct resource declaration 16 | module virtualNetwork 'br/public:avm/res/network/virtual-network:0.6.1' = { 17 | name: 'vnet-deployment' 18 | params: { 19 | // Required parameters 20 | name: vNetName 21 | addressPrefixes: [ 22 | '10.0.0.0/16' 23 | ] 24 | // Non-required parameters 25 | location: location 26 | tags: tags 27 | subnets: [ 28 | { 29 | name: peSubnetName 30 | addressPrefix: '10.0.1.0/24' 31 | privateEndpointNetworkPolicies: 'Disabled' 32 | privateLinkServiceNetworkPolicies: 'Enabled' 33 | } 34 | { 35 | name: appSubnetName 36 | addressPrefix: '10.0.2.0/24' 37 | privateEndpointNetworkPolicies: 'Disabled' 38 | privateLinkServiceNetworkPolicies: 'Enabled' 39 | delegation: 'Microsoft.App/environments' 40 | } 41 | ] 42 | } 43 | } 44 | 45 | output peSubnetName string = peSubnetName 46 | output peSubnetID string = '${virtualNetwork.outputs.resourceId}/subnets/${peSubnetName}' 47 | output appSubnetName string = appSubnetName 48 | output appSubnetID string = '${virtualNetwork.outputs.resourceId}/subnets/${appSubnetName}' 49 | -------------------------------------------------------------------------------- /infra/main.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'subscription' 2 | 3 | @minLength(1) 4 | @maxLength(64) 5 | @description('Name of the the environment which is used to generate a short unique hash used in all resources.') 6 | param environmentName string 7 | 8 | @minLength(1) 9 | @description('Primary location for all resources & Flex Consumption Function App') 10 | @allowed([ 11 | 'australiaeast' 12 | 'australiasoutheast' 13 | 'brazilsouth' 14 | 'canadacentral' 15 | 'centralindia' 16 | 'centralus' 17 | 'eastasia' 18 | 'eastus' 19 | 'eastus2' 20 | 'eastus2euap' 21 | 'francecentral' 22 | 'germanywestcentral' 23 | 'italynorth' 24 | 'japaneast' 25 | 'koreacentral' 26 | 'northcentralus' 27 | 'northeurope' 28 | 'norwayeast' 29 | 'southafricanorth' 30 | 'southcentralus' 31 | 'southeastasia' 32 | 'southindia' 33 | 'spaincentral' 34 | 'swedencentral' 35 | 'uaenorth' 36 | 'uksouth' 37 | 'ukwest' 38 | 'westcentralus' 39 | 'westeurope' 40 | 'westus' 41 | 'westus2' 42 | 'westus3' 43 | ]) 44 | @metadata({ 45 | azd: { 46 | type: 'location' 47 | } 48 | }) 49 | param location string 50 | param vnetEnabled bool 51 | param apiServiceName string = '' 52 | param apiUserAssignedIdentityName string = '' 53 | param applicationInsightsName string = '' 54 | param appServicePlanName string = '' 55 | param logAnalyticsName string = '' 56 | param resourceGroupName string = '' 57 | param storageAccountName string = '' 58 | param vNetName string = '' 59 | @description('Id of the user identity to be used for testing and debugging. This is not required in production. Leave empty if not needed.') 60 | param principalId string = deployer().objectId 61 | 62 | var abbrs = loadJsonContent('./abbreviations.json') 63 | var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) 64 | var tags = { 'azd-env-name': environmentName } 65 | var functionAppName = !empty(apiServiceName) ? apiServiceName : '${abbrs.webSitesFunctions}api-${resourceToken}' 66 | var deploymentStorageContainerName = 'app-package-${take(functionAppName, 32)}-${take(toLower(uniqueString(functionAppName, resourceToken)), 7)}' 67 | 68 | // Organize resources in a resource group 69 | resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { 70 | name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}' 71 | location: location 72 | tags: tags 73 | } 74 | 75 | // User assigned managed identity to be used by the function app to reach storage and other dependencies 76 | // Assign specific roles to this identity in the RBAC module 77 | module apiUserAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.1' = { 78 | name: 'apiUserAssignedIdentity' 79 | scope: rg 80 | params: { 81 | location: location 82 | tags: tags 83 | name: !empty(apiUserAssignedIdentityName) ? apiUserAssignedIdentityName : '${abbrs.managedIdentityUserAssignedIdentities}api-${resourceToken}' 84 | } 85 | } 86 | 87 | // Create an App Service Plan to group applications under the same payment plan and SKU 88 | module appServicePlan 'br/public:avm/res/web/serverfarm:0.1.1' = { 89 | name: 'appserviceplan' 90 | scope: rg 91 | params: { 92 | name: !empty(appServicePlanName) ? appServicePlanName : '${abbrs.webServerFarms}${resourceToken}' 93 | sku: { 94 | name: 'FC1' 95 | tier: 'FlexConsumption' 96 | } 97 | reserved: true 98 | location: location 99 | tags: tags 100 | } 101 | } 102 | 103 | module api './app/api.bicep' = { 104 | name: 'api' 105 | scope: rg 106 | params: { 107 | name: functionAppName 108 | location: location 109 | tags: tags 110 | applicationInsightsName: monitoring.outputs.name 111 | appServicePlanId: appServicePlan.outputs.resourceId 112 | runtimeName: 'dotnet-isolated' 113 | runtimeVersion: '8.0' 114 | storageAccountName: storage.outputs.name 115 | enableBlob: storageEndpointConfig.enableBlob 116 | enableQueue: storageEndpointConfig.enableQueue 117 | enableTable: storageEndpointConfig.enableTable 118 | deploymentStorageContainerName: deploymentStorageContainerName 119 | identityId: apiUserAssignedIdentity.outputs.resourceId 120 | identityClientId: apiUserAssignedIdentity.outputs.clientId 121 | appSettings: { 122 | } 123 | virtualNetworkSubnetId: vnetEnabled ? serviceVirtualNetwork.outputs.appSubnetID : '' 124 | } 125 | } 126 | 127 | // Backing storage for Azure functions backend API 128 | module storage 'br/public:avm/res/storage/storage-account:0.8.3' = { 129 | name: 'storage' 130 | scope: rg 131 | params: { 132 | name: !empty(storageAccountName) ? storageAccountName : '${abbrs.storageStorageAccounts}${resourceToken}' 133 | allowBlobPublicAccess: false 134 | allowSharedKeyAccess: false // Disable local authentication methods as per policy 135 | dnsEndpointType: 'Standard' 136 | publicNetworkAccess: vnetEnabled ? 'Disabled' : 'Enabled' 137 | networkAcls: vnetEnabled ? { 138 | defaultAction: 'Deny' 139 | bypass: 'None' 140 | } : { 141 | defaultAction: 'Allow' 142 | bypass: 'AzureServices' 143 | } 144 | blobServices: { 145 | containers: [{name: deploymentStorageContainerName}] 146 | } 147 | minimumTlsVersion: 'TLS1_2' // Enforcing TLS 1.2 for better security 148 | location: location 149 | tags: tags 150 | } 151 | } 152 | 153 | // Define the configuration object locally to pass to the modules 154 | var storageEndpointConfig = { 155 | enableBlob: true // Required for AzureWebJobsStorage, .zip deployment, Event Hubs trigger and Timer trigger checkpointing 156 | enableQueue: false // Required for Durable Functions and MCP trigger 157 | enableTable: false // Required for Durable Functions and OpenAI triggers and bindings 158 | enableFiles: false // Not required, used in legacy scenarios 159 | allowUserIdentityPrincipal: true // Allow interactive user identity to access for testing and debugging 160 | } 161 | 162 | // Consolidated Role Assignments 163 | module rbac 'app/rbac.bicep' = { 164 | name: 'rbacAssignments' 165 | scope: rg 166 | params: { 167 | storageAccountName: storage.outputs.name 168 | appInsightsName: monitoring.outputs.name 169 | managedIdentityPrincipalId: apiUserAssignedIdentity.outputs.principalId 170 | userIdentityPrincipalId: principalId 171 | enableBlob: storageEndpointConfig.enableBlob 172 | enableQueue: storageEndpointConfig.enableQueue 173 | enableTable: storageEndpointConfig.enableTable 174 | allowUserIdentityPrincipal: storageEndpointConfig.allowUserIdentityPrincipal 175 | } 176 | } 177 | 178 | // Virtual Network & private endpoint to blob storage 179 | module serviceVirtualNetwork 'app/vnet.bicep' = if (vnetEnabled) { 180 | name: 'serviceVirtualNetwork' 181 | scope: rg 182 | params: { 183 | location: location 184 | tags: tags 185 | vNetName: !empty(vNetName) ? vNetName : '${abbrs.networkVirtualNetworks}${resourceToken}' 186 | } 187 | } 188 | 189 | module storagePrivateEndpoint 'app/storage-PrivateEndpoint.bicep' = if (vnetEnabled) { 190 | name: 'servicePrivateEndpoint' 191 | scope: rg 192 | params: { 193 | location: location 194 | tags: tags 195 | virtualNetworkName: !empty(vNetName) ? vNetName : '${abbrs.networkVirtualNetworks}${resourceToken}' 196 | subnetName: vnetEnabled ? serviceVirtualNetwork.outputs.peSubnetName : '' // Keep conditional check for safety, though module won't run if !vnetEnabled 197 | resourceName: storage.outputs.name 198 | enableBlob: storageEndpointConfig.enableBlob 199 | enableQueue: storageEndpointConfig.enableQueue 200 | enableTable: storageEndpointConfig.enableTable 201 | } 202 | } 203 | 204 | // Monitor application with Azure Monitor - Log Analytics and Application Insights 205 | module logAnalytics 'br/public:avm/res/operational-insights/workspace:0.11.1' = { 206 | name: '${uniqueString(deployment().name, location)}-loganalytics' 207 | scope: rg 208 | params: { 209 | name: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}' 210 | location: location 211 | tags: tags 212 | dataRetention: 30 213 | } 214 | } 215 | 216 | module monitoring 'br/public:avm/res/insights/component:0.6.0' = { 217 | name: '${uniqueString(deployment().name, location)}-appinsights' 218 | scope: rg 219 | params: { 220 | name: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}' 221 | location: location 222 | tags: tags 223 | workspaceResourceId: logAnalytics.outputs.resourceId 224 | disableLocalAuth: true 225 | } 226 | } 227 | 228 | // App outputs 229 | output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.name 230 | output AZURE_LOCATION string = location 231 | output AZURE_TENANT_ID string = tenant().tenantId 232 | output SERVICE_API_NAME string = api.outputs.SERVICE_API_NAME 233 | output AZURE_FUNCTION_NAME string = api.outputs.SERVICE_API_NAME 234 | -------------------------------------------------------------------------------- /infra/main.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "environmentName": { 6 | "value": "${AZURE_ENV_NAME}" 7 | }, 8 | "location": { 9 | "value": "${AZURE_LOCATION}" 10 | }, 11 | "principalId": { 12 | "value": "${AZURE_PRINCIPAL_ID}" 13 | }, 14 | "vnetEnabled": { 15 | "value": "${VNET_ENABLED}" 16 | } 17 | } 18 | } --------------------------------------------------------------------------------