avatarBob Code

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

14201

Abstract

d it in ADO in your previous stage.</p><p id="e05f">DownloadBuildArtifacts@0 — Download build artifacts v0 task</p><figure id="c5ac"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*zeiqhdLN-dwc5l99CQmdxg.png"><figcaption></figcaption></figure><figure id="7d9e"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*tvjSF99xBJISFwQcDXT_Bw.png"><figcaption></figcaption></figure><h1 id="7fcd">#2 Deploy your Function App</h1><div id="8ca4"><pre> <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">AzureFunctionApp@1</span> <span class="hljs-attr">inputs:</span> <span class="hljs-attr">azureSubscription:</span> <span class="hljs-string">(ServiceConnectionName)</span> <span class="hljs-comment"># actually the service connection name</span> <span class="hljs-attr">appType:</span> <span class="hljs-string">functionApp</span> <span class="hljs-comment"># for windows function app</span> <span class="hljs-attr">appName:</span> <span class="hljs-string">(routingAppName){{</span> <span class="hljs-string">parameters.env</span> <span class="hljs-string">}}</span> <span class="hljs-attr">package:</span> <span class="hljs-string">(System.ArtifactsDirectory)/DionysosRouter/Dionysos-Router.zip</span> <span class="hljs-attr">deploymentMethod:</span> <span class="hljs-string">'zipDeploy'</span> <span class="hljs-comment"># Zip deployment involves packaging the application code and dependencies into a ZIP file and deploying it to the Azure Functions app.</span></pre></div><p id="da3c">The function app must have been previously created (see the terraform deployment blog > <a href="https://readmedium.com/terraform-in-azure-devops-pipeline-9a7e7dce2c05">Terraform in Azure DevOps Pipeline</a>)</p><p id="158e">This final stage will take the dotnet project file that has been built and saved in a zip file as artifact.</p><p id="423d">We now simply have to mention it in our function app task and the code will run on the function app!</p><h2 id="32a9">How to find the package file path of the artifact in Azure DevOps?</h2><p id="9dea">First add (System.ArtifactsDirectory) or (Build.ArtifactStagingDirectory)</p><p id="07f2">Then add the name of the folder you created</p><p id="cbef">Finally input the name of your zip file</p><h1 id="eb6f">Errors</h1><h2 id="79e8">Error: cannot find any artifacts after DownloadBuildArtifacts</h2><p id="806e">Micosoft recently updated the DownloadBuildArtifacts task</p><p id="8916">It so happens that in the UI, it would seem that there is a folder with the name of the artifact.</p><p id="3115">Actually, there isn’t, so all your files are directly available from the System.ArtifactsDirectory (or wherever you downloaded the files)</p><div id="736e"><pre>
<span class="hljs-comment">############################## Artifacts ##################################</span> <span class="hljs-attr">steps:</span> <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">DownloadBuildArtifacts@1</span> <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Download Build Artifacts'</span> <span class="hljs-attr">inputs:</span> <span class="hljs-attr">buildType:</span> <span class="hljs-string">'current'</span> <span class="hljs-attr">downloadType:</span> <span class="hljs-string">'single'</span> <span class="hljs-attr">artifactName:</span> <span class="hljs-string">{{</span> <span class="hljs-string">parameters.solutionName</span> <span class="hljs-string">}}_artifacts</span> <span class="hljs-attr">downloadPath:</span> <span class="hljs-string">'(System.ArtifactsDirectory)'</span>

<span class="hljs-comment"># Displays the downloaded files</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">powershell:</span> <span class="hljs-string">|
    $downloadedFiles = Get-ChildItem "$(System.ArtifactsDirectory)" -Recurse
    Write-Output "Downloaded Files:"
    foreach ($file in $downloadedFiles) {
      Write-Output $file.FullName}

</span> <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Display Downloaded Artifact Names'</span>

<span class="hljs-comment">############################## Release the API beast ##################################</span> <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">AzureWebApp@1</span> <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Release the API beast'</span> <span class="hljs-attr">inputs:</span> <span class="hljs-attr">azureSubscription:</span> <span class="hljs-string">(serviceConnection)</span> <span class="hljs-attr">appName:</span> <span class="hljs-string">'plutus-pnp-webapp-d'</span> <span class="hljs-attr">package:</span> <span class="hljs-string">'(System.ArtifactsDirectory)\Plutus.ProductPricing.API.zip'</span> <span class="hljs-attr">deploymentMethod:</span> <span class="hljs-string">'zipDeploy'</span></pre></div><p id="8f46">Read more to see other workarounds</p><div id="47e7" class="link-block"> <a href="https://github.com/Microsoft/azure-pipelines-tasks/issues/6739"> <div> <div> <h2>DownloadBuildArtifacts directly to a folder (without the artifact name) · Issue #6739 ·…</h2> <div><h3>Troubleshooting Checkout how to troubleshoot failures and collect debug logs…</h3></div> <div><p>github.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*aXDVyeskg7HPBsjR)"></div> </div> </div> </a> </div><h2 id="98db">Error: Azure functions don’t appear/ cannot be run in the Azure portal</h2><p id="73e1">Try running with ZipDeploy, this might not work with .Net 8 versions at time of writing (early 2024)</p><p id="9f49">Solution: use run from package</p><h2 id="8d91">Code</h2><p id="1058">For the explanations and other options, scroll after the code</p><p id="f900">#1 In your terraform code, set the appsettings for your func</p><div id="4c81"><pre>resource <span class="hljs-string">"azurerm_windows_function_app"</span> <span class="hljs-string">"pnp-function"</span> { name = <span class="hljs-string">"namefunc"</span> resource_group_name = azurerm_resource_group.rg.<span class="hljs-type">name</span> <span class="hljs-variable">location</span> <span class="hljs-operator">=</span> azurerm_resource_group.rg.<span class="hljs-type">location</span>

<span class="hljs-variable">storage_account_name</span> <span class="hljs-operator">=</span> azurerm_storage_account.pnp-sa-functions.<span class="hljs-type">name</span> <span class="hljs-variable">storage_account_access_key</span> <span class="hljs-operator">=</span> azurerm_storage_account.pnp-sa-functions.<span class="hljs-type">primary_access_key</span> <span class="hljs-variable">service_plan_id</span> <span class="hljs-operator">=</span> azurerm_service_plan.pnp-asp-functions.<span class="hljs-type">id</span>

<span class="hljs-variable">app_settings</span> <span class="hljs-operator">=</span> {
    WEBSITE_RUN_FROM_PACKAGE = <span class="hljs-number">1</span>,
    WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED = <span class="hljs-number">1</span>,
    WEBSITE_ENABLE_SYNC_UPDATE_SITE = <span class="hljs-literal">true</span>,
    FUNCTIONS_WORKER_RUNTIME = <span class="hljs-string">"dotnet-isolated"</span>

}</pre></div><ul><li>WEBSITE_RUN_FROM_PACKAGE: app will run from package instead of zip</li><li>WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED = .Net 8+ function apps must run in dotnet isolated</li></ul><p id="5786">#2 In your publish pipeline, Publish your csproj, keep zipAfterPublish as true</p><div id="050f"><pre> <span class="hljs-comment"># Publish build results Func</span> <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">DotNetCoreCLI@2</span> <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Generate Build Artifacts Func'</span> <span class="hljs-attr">inputs:</span> <span class="hljs-attr">command:</span> <span class="hljs-string">publish</span> <span class="hljs-attr">publishWebProjects:</span> <span class="hljs-literal">False</span> <span class="hljs-attr">projects:</span> <span class="hljs-string">'**\nameofyourproj.csproj'</span> <span class="hljs-attr">arguments:</span> <span class="hljs-string">'--configuration <span class="hljs-template-variable">{{ parameters.buildConfiguration }}</span> --output (Build.ArtifactStagingDirectory)'</span> <span class="hljs-attr">zipAfterPublish:</span> <span class="hljs-literal">true</span></pre></div><p id="cf35">#3 In your release pipeline</p><div id="b1d2"><pre> <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">AzureFunctionApp@2</span> <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Release Func'</span> <span class="hljs-attr">inputs:</span> <span class="hljs-attr">connectedServiceNameARM:</span> <span class="hljs-string">(ServiceConnectionname)</span> <span class="hljs-attr">appType:</span> <span class="hljs-string">functionApp</span> <span class="hljs-comment"># for windows function app</span> <span class="hljs-attr">appName:</span> <span class="hljs-string">'nameofyourfuncapp'</span> <span class="hljs-attr">package:</span> <span class="hljs-string">'(System.ArtifactsDirectory)\nameofyourproj.zip'</span> <span class="hljs-attr">deploymentMethod:</span> <span class="hljs-string">'runFromPackage'</span></pre></div><p id="28cc">deploymentMethod: ‘runFromPackage’ = instead of running Zip, you will directly run from package</p><h2 id="9e6e">Explanations</h2><p id="2bf1">The /wwroot folder of your app must look like this</p><div id="3789"><pre> | - <span class="hljs-built_in">bin</span> | - MyFunction | - host.json</pre></div><p id="41ee">You can find your folder structure in the portal by:</p><ul><li>Go to your Function app</li><li>Go to App Service Editor (Preview)</li><li>On the top, open the Kudu Console</li></ul><figure id="6452"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*h1F1BgbsQ-z23OLNbd6Rgw.png"><figcaption></figcaption></figure><ul><li>Navigate to site > /wwwroot</li></ul><p id="938e">A bin folder contains packages and other library files that the function app requires. Specific folder structures required by the function app depend on language: C# compiled (. csproj)</p><p id="cd1c">Is your bin file missing? Then you have two options:</p><ul><li>Manually add the bin folder to your wwwroot in your pipeline</li><li>Run the function directly from the deployment package file (see code example above)</li></ul><p id="266b">Also, if you’re runninga .net 8+ function app, make sure that you have the isolated worker model enabled (<a href="https://azure.microsoft.com/en-us/updates/ga-azure-functions-supports-net-8-in-the-isolated-worker-model/">https://azure.microsoft.com/en-us/updates/ga-azure-functions-supports-net-8-in-the-isolated-worker-model/</a>)</p><p id="ff29"><b>Running from Deployment Package File</b></p><p id="5769">You can also choose to run your functions directly from the deployment package file. This method skips the deployment step of copying files from the package to the wwwroot directory of your function app. Instead, the package file is mounted by the Functions runtime, and the contents of the wwwroot directory become read-only.</p><p id="096d">Zip deployment integrates with this feature, which you can enable by setting the function app setting WEBSITE_RUN_FROM_PACKAGE to a value of 1. For more information, see Run your functions from a deployment package file.</p><p id="5518"><a href="https://learn.microsoft.com/en-us/azure/azure-functions/run-functions-from-deployment-package">https://learn.microsoft.com/en-us/azure/azure-functions/run-functions-from-deployment-package</a></p><p id="80bf">When you set WEBSITE_RUN_FROM_PACKAGE = 1, the .zip file is mounted directly, and its contents are not extracted to the wwwroot directory. As a result, wwwroot becomes read-only. But, whether it’s “empty” is misleading; it may have other system files or old content, but it will be accurate to say that it won’t have the active content from the current .zip package. The active content is directly read from the mounted .zip package. (<a href="https://stackoverflow.com/questions/76867490/azure-function-clarifications-about-zip-deployment-and-run-from-package-file">https://stackoverflow.com/questions/76867490/azure-function-clarifications-about-zip-deployment-and-run-from-package-file</a>)</p><div id="4137" class="link-block"> <a href="https://learn.microsoft.com/en-us/azure/azure-functions/run-functions-from-deployment-package"> <div> <div> <h2>Run your functions from a package file in Azure</h2> <div><h3>Have the Azure Functions runtime run your functions by mounting a deployment package file that contains your function…</h3></div> <div><p>learn.microsoft.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*Rbvci2gmPqcd6taa)"></div> </div> </div> </a> </div><p id="5400"><b>Manually add proj to bin</b></p><ul><li>Done using the archive task and specifying the wwwroot folder</li></ul><p id="b0fd">Read about ArchiveFiles task</p><div id="7131" class="link-block"> <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/archive-files-v2?view=azure-pipelines"> <d

Options

iv> <div> <h2>ArchiveFiles@2 - Archive files v2 task</h2> <div><h3>Compress files into .7z, .tar.gz, or .zip.</h3></div> <div><p>learn.microsoft.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*dviHww2yLkDwtRAB)"></div> </div> </div> </a> </div><p id="83a4">Understand how Zip deployment works</p><div id="6f67" class="link-block"> <a href="https://learn.microsoft.com/en-us/azure/azure-functions/deployment-zip-push"> <div> <div> <h2>Zip push deployment for Azure Functions</h2> <div><h3>Use the .zip file deployment facilities of the Kudu deployment service to publish your Azure Functions.</h3></div> <div><p>learn.microsoft.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*8dnp0FYLNjs-Obk_)"></div> </div> </div> </a> </div><p id="1ee3">Discussion about how to solve error including archive steps</p><div id="3c36" class="link-block"> <a href="https://stackoverflow.com/questions/56105855/azure-functions-not-showing-up-in-function-app-in-portal"> <div> <div> <h2>Azure Functions not showing up in Function app in portal</h2> <div><h3>I’ve got a solution that contains a web app and class libraries. I added two Azure function projects to this, the first…</h3></div> <div><p>stackoverflow.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*KHIGgbF58zS_l3bL)"></div> </div> </div> </a> </div><p id="12cb">Fix about the bin folder structure</p><div id="83f2" class="link-block"> <a href="https://learn.microsoft.com/en-us/answers/questions/1255928/how-to-fix-the-azure-functions-not-showing-up-in-t"> <div> <div> <h2>How to fix the azure functions not showing up in the portal issue? — Microsoft Q&A</h2> <div><h3>Hi I am facing an issue where functions inside my azure function app don’t show up on the portal after deployment. I…</h3></div> <div><p>learn.microsoft.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*5UuhdO61KtDyLXjG)"></div> </div> </div> </a> </div><p id="4501">Look at the Azure pipeline steps for build and publish</p><div id="829f" class="link-block"> <a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-class-library?tabs=v4%2Ccmd#functions-class-library-project"> <div> <div> <h2>Develop C# class library functions using Azure Functions</h2> <div><h3>Understand how to use C# to develop and publish code as class libraries that run in-process with the Azure Functions…</h3></div> <div><p>learn.microsoft.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*6bcWNoViQaE-1aeq)"></div> </div> </div> </a> </div><p id="f8ab">Documentation on publish path bin\Release\net8.0\publish</p><div id="be4d" class="link-block"> <a href="https://learn.microsoft.com/en-us/dotnet/core/tutorials/publishing-with-visual-studio?pivots=dotnet-8-0"> <div> <div> <h2>Publish a .NET console application using Visual Studio - .NET</h2> <div><h3>Learn how to use Visual Studio to create the set of files that are needed to run a .NET application.</h3></div> <div><p>learn.microsoft.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*ebLuMrrD8Bsa0RQ6)"></div> </div> </div> </a> </div><h2 id="44cb">##[error]TypeError: Cannot read properties of undefined (reading ‘PreDeploymentStep’)</h2><p id="9773">Means that some of the app-settings that are in your app-service cannot be read</p><p id="3128">Solution:</p><p id="3801">Either remove some app-settings that actually are not yet supported or find the latest supported version</p><div id="9e2c" class="link-block"> <a href="https://github.com/microsoft/azure-pipelines-tasks/blob/30c99c8/Tasks/AzureWebAppV1/deploymentProvider/AzureRmWebAppDeploymentProvider.ts#L23"> <div> <div> <h2>azure-pipelines-tasks/Tasks/AzureWebAppV1/deploymentProvider/AzureRmWebAppDeploymentProvider.ts at…</h2> <div><h3>Tasks for Azure Pipelines. Contribute to microsoft/azure-pipelines-tasks development by creating an account on GitHub.</h3></div> <div><p>github.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*Kg7Wgw8ha7fjPaRm)"></div> </div> </div> </a> </div><div id="d916" class="link-block"> <a href="https://stackoverflow.com/questions/69896255/azure-web-app-deploy-fails-with-cannot-read-property-updatestartupcommandandrun"> <div> <div> <h2>Azure Web App Deploy fails with Cannot read property 'updateStartupCommandAndRuntimeStack' of…</h2> <div><h3>Trying to deploy dotnet core 5 app to Azure linux web app from Azure devops. Building works fine, but the deploy step…</h3></div> <div><p>stackoverflow.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*xC4g644hhpHQOf5q)"></div> </div> </div> </a> </div><h2 id="647a">Error: My ServicePrincipal in Azure DevOps cannot reach the FunctionApp that sits behind a vnet/subnet/private endpoint</h2><p id="0b3c">Solution:</p><ul><li>#1 Add network rule to allow SP to deploy function app</li></ul><div id="1b97"><pre><span class="hljs-comment">## Add release agent to access rules of function</span> <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">AzureCLI@2</span> <span class="hljs-attr">inputs:</span> <span class="hljs-attr">azureSubscription:</span> <span class="hljs-string">{{</span> <span class="hljs-string">parameters.serviceConnection</span> <span class="hljs-string">}}</span> <span class="hljs-attr">connectedServiceNameARM:</span> <span class="hljs-string">{{</span> <span class="hljs-string">parameters.serviceConnection</span> <span class="hljs-string">}}</span> <span class="hljs-attr">scriptType:</span> <span class="hljs-string">'pscore'</span> <span class="hljs-attr">scriptLocation:</span> <span class="hljs-string">'inlineScript'</span> <span class="hljs-attr">addSpnToEnvironment:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># boolean. Access service principal details in script. Default: false.</span> <span class="hljs-attr">inlineScript:</span> <span class="hljs-string">| ip = (Invoke-WebRequest -uri "http://ifconfig.me/ip").Content Write-Host Current Agent IP is ip az functionapp config access-restriction add -g '{{ parameters.resourceGroupName }}{{ parameters.environment }}' -n 'plutus-store-func-livediscounts-{{ parameters.environment }}' --rule-name devopsagent --action Allow --ip-address ip --priority 200 az functionapp config appsettings set --name 'plutus-store-func-livediscounts-{{ parameters.environment }}' --resource-group '{{ parameters.resourceGroupName }}${{ parameters.environment }}' --settings WEBSITE_RUN_FROM_PACKAGE=1</span></pre></div><ul><li>#2 Deploy function app (see previous sections)</li><li>#3 Remove network rule</li></ul><div id="f066"><pre><span class="hljs-comment">## Remove release agent from access rules</span>

  • task: AzureCLI@2 inputs: azureSubscription: <span class="hljs-variable">{{ parameters.serviceConnection }</span>} connectedServiceNameARM: <span class="hljs-variable">{{ parameters.serviceConnection }</span>} scriptType: <span class="hljs-string">'pscore'</span> scriptLocation: <span class="hljs-string">'inlineScript'</span> addSpnToEnvironment: <span class="hljs-literal">true</span> <span class="hljs-comment"># boolean. Access service principal details in script. Default: false.</span> inlineScript: | <span class="hljs-variable">ip</span> = (Invoke-WebRequest -uri <span class="hljs-string">"http://ifconfig.me/ip"</span>).Content Write-Host Current Agent IP is <span class="hljs-variable">ip</span> az functionapp config access-restriction remove -g <span class="hljs-string">'{{ parameters.resourceGroupName }}{{ parameters.environment }}'</span> -n <span class="hljs-string">'plutus-store-func-livediscounts-{{ parameters.environment }}'</span> --rule-name devopsagent</pre></div><p id="57f2"><b>Error: C:\hostedtoolcache\windows\dotnet\sdk\8.0.204\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.ConflictResolution.targets(112,5): error NETSDK1152: Found multiple publish output files with the same relative path:</b></p><p id="3486">Solution, append \NameOfYourFunc to arguments output</p><p id="3ba9">This way the files will be saved in a bespoke folder</p><div id="3b71"><pre> <span class="hljs-comment"># Publish build results Func LiveDiscounts</span> <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">DotNetCoreCLI@2</span> <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Generate Build Artifacts StoreServe LiveDiscounts Func'</span> <span class="hljs-attr">inputs:</span> <span class="hljs-attr">command:</span> <span class="hljs-string">publish</span> <span class="hljs-attr">publishWebProjects:</span> <span class="hljs-literal">False</span> <span class="hljs-attr">projects:</span> <span class="hljs-string">'**\Plutus.StoreServices.LiveDiscounts.Http.csproj'</span> <span class="hljs-attr">arguments:</span> <span class="hljs-string">'--configuration <span class="hljs-template-variable">{{ parameters.buildConfiguration }}</span> --output (Build.ArtifactStagingDirectory)\LiveDiscounts'</span> <span class="hljs-attr">zipAfterPublish:</span> <span class="hljs-literal">true</span></pre></div><p id="75b3">And change the release package</p><div id="4aa5"><pre> <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">AzureFunctionApp@1</span> <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Release Live discounts Func'</span> <span class="hljs-attr">inputs:</span> <span class="hljs-attr">azureSubscription:</span> <span class="hljs-string">{{</span> <span class="hljs-string">parameters.serviceConnection</span> <span class="hljs-string">}}</span> <span class="hljs-comment">#connectedServiceNameARM: {{ parameters.serviceConnection }}</span> <span class="hljs-attr">appType:</span> <span class="hljs-string">functionApp</span> <span class="hljs-comment"># for windows function app</span> <span class="hljs-attr">appName:</span> <span class="hljs-string">'plutus-store-func-livediscounts-<span class="hljs-template-variable">{{ parameters.environment }}</span>'</span> <span class="hljs-attr">package:</span> <span class="hljs-string">'$(System.ArtifactsDirectory)\LiveDiscounts\Plutus.StoreServices.LiveDiscounts.Http.zip'</span> <span class="hljs-attr">deploymentMethod:</span> <span class="hljs-string">'runFromPackage'</span></pre></div><p id="106a">Finally add to your csproj file</p><div id="9ee2"><pre><span class="hljs-tag"><<span class="hljs-name">PropertyGroup</span>></span> <span class="hljs-tag"><<span class="hljs-name">ErrorOnDuplicatePublishOutputFiles</span>></span>false<span class="hljs-tag"></<span class="hljs-name">ErrorOnDuplicatePublishOutputFiles</span>></span> <span class="hljs-tag"></<span class="hljs-name">PropertyGroup</span>></span></pre></div><p id="38ae"><b>Understanding the Issues</b></p><ul><li>When you publish multiple Azure Functions projects to the same directory, and these projects have files with the same name (like <code>host.json</code>, <code>local.settings.json</code>, etc.), the .NET SDK cannot resolve which file to keep and which to overwrite, resulting in the <code>NETSDK1152</code> error.</li><li>In your specific scenario, both <code>Plutus.StoreServices.LiveDiscounts.Http</code> and <code>Plutus.StoreServices.BulkPrices</code> projects have their own <code>host.json</code> files. When you try to publish these projects to the same output directory, the conflict arises.</li><li>There has been a breaking change see below</li></ul><div id="e706" class="link-block"> <a href="https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/6.0/duplicate-files-in-output#recommended-action"> <div> <div> <h2>Breaking change: Generate error for duplicate files in publish output - .NET</h2> <div><h3>Learn about the breaking change in .NET 6 where the .NET SDK generates an error when files from different source paths…</h3></div> <div><p>learn.microsoft.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*IINEj5a9243eAb69)"></div> </div> </div> </a> </div></article></body>

Deploying .Net Code to a Function App using YAML files in Azure DevOps

Introduction

The DotNet Pipeline is part of the Continuous Delivery Process during which code is built, tested, and deployed to one or more test and production environments. Deploying and testing in multiple environments increases quality.

Deploying a .NET Function App using YAML files in Azure DevOps is a streamlined and efficient way to automate the deployment process of your serverless functions.

Azure DevOps, in combination with YAML configuration files, empowers you to define the deployment steps, dependencies, and configurations as code, making it easier to maintain and reproduce deployments. In this process, you can manage the entire lifecycle of your .NET Function App, from building and packaging your functions to deploying them seamlessly to the Azure cloud. This approach provides greater control, reliability, and scalability for your serverless applications, ensuring they’re ready to serve your users and customers without manual intervention.

Notes

This blog focuses only on the .Net code deployment, if you would like to create the function app itself, please refer to my other blog:

Terraform in Azure DevOps Pipeline

Build vs Release Pipeline

The build pipeline is meant to produce the binaries (such as executing commands like ‘dotnet publish’ or ‘ng build — prod’) as an artifact, stored in a file that the release pipeline will then download and use and deploy.

The rationale behind maintaining separate build and release pipelines is to ensure that a specific version of software is built only once, and then these identical binaries could be used across various target environments, such as development, testing, and production.

The release pipeline on the other hand will deploy the artifact that has been created by the build pipeline to the given function app.

#1 Get the correct dotnet version of your project

    steps:
    - task: UseDotNet@2
      displayName: 'Use DotNet 6'  
      inputs:
        packageType: 'sdk'
        version: '6.0.415'
        installationPath: $(Agent.ToolsDirectory)/dotnet

First we need to install the dotnet version onto which the project run, you can run dotnet — info to find out which version you are using.

#2 Build the Project

    - task: DotNetCoreCLI@2
      displayName: 'Dotnet build Release'
      inputs:
        command: 'build'
        projects: '**/DionysosRouter.csproj'
        arguments: '--configuration Release'   

Run the build command to compile the .NET project.

The dotnet build command builds the project and its dependencies into a set of binaries. The binaries include the project’s code in Intermediate Language (IL) files with a .dll extension.

[Build .net core 6 projects with Azure Devops using yml files](https://briancaos.wordpress.com/2022/09/22/build-net-core-6-projects-with-azure-devops-using-yml-files/)

[dotnet build](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-build)

What does Build do?

1/ The compiler launches, it first restores all required dependencies through the Nuget Package Manager.

2/ Then the command-line tool compiles the project and stores its output in a folder called “bin” (Debug or Release)

3/ At this point the C# Code has been compiled into an executable file containing the Common Intermediate Language (CIL) Code — Assembly Level

4/ For the OS to run the CIL it needs to be converted into native code which is done by the Common Language Runtime

[How is C# Compiled?](https://freecontent.manning.com/how-is-c-compiled/)

#3 Restore dependencies

- task: DotNetCoreCLI@2
      displayName: Restore dotnet tools
      inputs:
        command: custom
        custom: tool
        arguments: restore

Ensures that your project’s dependencies are correctly resolved and downloaded

With NuGet Package Restore you can install all your project’s dependency without having to store them in source control. This allows for a cleaner development environment and a smaller repository size. You can restore your NuGet packages using the NuGet restore task, the NuGet CLI, or the .NET Core CLI

[Restore NuGet packages with Azure Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/packages/nuget-restore?view=azure-devops&tabs=classic)

What dependencies does the command restore?

Dependency Resolution: .NET projects typically rely on various external libraries and packages (NuGet packages in the case of .NET). These dependencies are listed in your project file (e.g., .csproj file) as references. When you run dotnet restore, it scans the project file, fetches the necessary dependencies, and makes them available for use in your project. This ensures that the correct versions of these packages are available.

The ‘restore’ command is used to restore NuGet packages referenced by the specified project(s).

It ensures that the required packages are downloaded and restored to the project’s packages directory.

[DotNetCoreCLI@2 — .NET Core v2 task](https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/dotnet-core-cli-v2?view=azure-pipelines)

dotnet restore — Restores the dependencies and tools of a project.

[dotnet restore](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-restore)

The dotnet build command builds the project and its dependencies into a set of binaries. The binaries include the project’s code in Intermediate Language (IL) files with a .dll extension.

The C# compilation process has three states (C#, Intermediate Language, and native code) and two stages: going from C# to Common Intermediate Language and going from Intermediate Language to native code.

#4 Publish

- task: DotNetCoreCLI@2
      displayName: 'Dotnet publish'
      inputs:
        command: publish
        publishWebProjects: false  # If this input is set to true, the projects property value is skipped, and the task tries to find the web projects (web.config file or a wwwroot folder) in the repository and run the publish command on them
        arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)' ## --configuration Release or Debug, output specifies the output directory for the published files to be
        zipAfterPublish: true # If this input is set to true, folders created by the publish command will be zipped and deleted.
        projects: '**/DionysosRouter.csproj' # specifies the project file to be published. It uses a wildcard pattern to find any project file with the name "DionysosRouter.csproj" in any subdirectory. This allows it to work with multiple project files if they exist.

The “publish” command is used to prepare the project for deployment by generating publishable output files.

sets the build configuration to “Release” and specifies the output directory as the “$(Build.ArtifactStagingDirectory).” This means the publish output will be placed in a location that Azure DevOps uses for staging artifacts.

zipAfterPublish: true: If this input is set to “true,” it will compress the folders created by the publish command into a zip file. This can be useful for packaging the published artifacts into a single archive.

[dotnet publish](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-publish)

#5 Publish artifacts

- task: PublishBuildArtifacts@1
      displayName: "Publish Artifacts"
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        artifactName: 'DionysosRouter'

So that we can use them at the release stage

dotnet publish — Publishes the application and its dependencies to a folder for deployment to a hosting system.

[dotnet publish](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-publish)

DotNetCoreCLI@2 > publish

In a pipeline we use the DotNetCoreCLI@2 — .NET Core v2 task which can Build, test, package, or publish a dotnet application, or run a custom dotnet command.

What are Artifacts?

[Artifacts in Azure Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/artifacts/build-artifacts?view=azure-devops&tabs=yaml)

Errors

/DotNetPipeline/Build.yml (Line: 1, Col: 1): Unexpected value ‘pool’

/DotNetPipeline/Build.yml (Line: 4, Col: 1): Unexpected value ‘steps’

Solution:

The pool and steps sections should be inside a job definition.

Error:

Stage deploy_dotnet must contain at least one job with no dependencies.

Solution:

Remove the dependsOn: Build

Error:

MSBUILD : error MSB1003: Specify a project or solution file. The current working directory does not contain a project or solution file.

MSBuild version 17.7.3+8ec440e68 for .NET

MSBUILD : error MSB1003: Specify a project or solution file. The current working directory does not contain a project or solution file.

Solution: adjust the directory path

Error:

Info: .NET Core SDK/runtime 2.2 and 3.0 are now End of Life(EOL) and have been removed from all hosted agents. If you’re using these SDK/runtimes on hosted agents,

kindly upgrade to newer versions which are not EOL, or else use UseDotNet task to install the required version.

Solution: specify the right dotnet version in the first task

Release Pipeline Steps

#1 Download the artifact from the build pipeline

    - task: DownloadBuildArtifacts@0
      displayName: 'Download Build Artifacts'
      inputs:
        buildType: 'current' # current,' meaning it will download artifacts from the current build.
        downloadType: 'single' # In this case, it's set to 'single,' which means it will download a single set of artifacts
        artifactName: 'DionysosRouter'
        downloadPath: '$(System.ArtifactsDirectory)' # This parameter indicates the directory where the downloaded artifacts will be stored

In the last stage we uploaded the arfifacts as a zip in a directory in ADO.

To use them we first need to download them.

If you are not sure about the exact location, you can find it in ADO in your previous stage.

[DownloadBuildArtifacts@0 — Download build artifacts v0 task](https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/download-build-artifacts-v0?view=azure-pipelines)

#2 Deploy your Function App

    - task: AzureFunctionApp@1
      inputs:
        azureSubscription: $(ServiceConnectionName) # actually the service connection name
        appType: functionApp # for windows function app
        appName: $(routingAppName)${{ parameters.env }}
        package: $(System.ArtifactsDirectory)/DionysosRouter/Dionysos-Router.zip
        deploymentMethod: 'zipDeploy' # Zip deployment involves packaging the application code and dependencies into a ZIP file and deploying it to the Azure Functions app.

The function app must have been previously created (see the terraform deployment blog > Terraform in Azure DevOps Pipeline)

This final stage will take the dotnet project file that has been built and saved in a zip file as artifact.

We now simply have to mention it in our function app task and the code will run on the function app!

How to find the package file path of the artifact in Azure DevOps?

First add $(System.ArtifactsDirectory) or $(Build.ArtifactStagingDirectory)

Then add the name of the folder you created

Finally input the name of your zip file

Errors

Error: cannot find any artifacts after DownloadBuildArtifacts

Micosoft recently updated the DownloadBuildArtifacts task

It so happens that in the UI, it would seem that there is a folder with the name of the artifact.

Actually, there isn’t, so all your files are directly available from the System.ArtifactsDirectory (or wherever you downloaded the files)

    
############################## Artifacts ##################################
    steps:
    - task: DownloadBuildArtifacts@1
      displayName: 'Download Build Artifacts'
      inputs:
        buildType: 'current'
        downloadType: 'single'
        artifactName: ${{ parameters.solutionName }}_artifacts
        downloadPath: '$(System.ArtifactsDirectory)'

    # Displays the downloaded files
    - powershell: |
        $downloadedFiles = Get-ChildItem "$(System.ArtifactsDirectory)" -Recurse
        Write-Output "Downloaded Files:"
        foreach ($file in $downloadedFiles) {
          Write-Output $file.FullName}
      displayName: 'Display Downloaded Artifact Names'

############################## Release the API beast ##################################
    - task: AzureWebApp@1
      displayName: 'Release the API beast'
      inputs:
        azureSubscription: $(serviceConnection)
        appName: 'plutus-pnp-webapp-d'
        package: '$(System.ArtifactsDirectory)\Plutus.ProductPricing.API.zip'
        deploymentMethod: 'zipDeploy'

Read more to see other workarounds

Error: Azure functions don’t appear/ cannot be run in the Azure portal

Try running with ZipDeploy, this might not work with .Net 8 versions at time of writing (early 2024)

Solution: use run from package

Code

For the explanations and other options, scroll after the code

#1 In your terraform code, set the appsettings for your func

resource "azurerm_windows_function_app" "pnp-function" {
  name                = "namefunc"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location

  storage_account_name       = azurerm_storage_account.pnp-sa-functions.name
  storage_account_access_key = azurerm_storage_account.pnp-sa-functions.primary_access_key
  service_plan_id            = azurerm_service_plan.pnp-asp-functions.id

    app_settings = {
        WEBSITE_RUN_FROM_PACKAGE = 1,
        WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED = 1,
        WEBSITE_ENABLE_SYNC_UPDATE_SITE = true,
        FUNCTIONS_WORKER_RUNTIME = "dotnet-isolated"
  }
  • WEBSITE_RUN_FROM_PACKAGE: app will run from package instead of zip
  • WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED = .Net 8+ function apps must run in dotnet isolated

#2 In your publish pipeline, Publish your csproj, keep zipAfterPublish as true

 # Publish build results Func
  - task: DotNetCoreCLI@2
    displayName: 'Generate Build Artifacts Func'
    inputs:
      command: publish
      publishWebProjects: False
      projects: '**\nameofyourproj.csproj'
      arguments: '--configuration  ${{ parameters.buildConfiguration }} --output $(Build.ArtifactStagingDirectory)'
      zipAfterPublish: true

#3 In your release pipeline

    - task: AzureFunctionApp@2
      displayName: 'Release Func'
      inputs:
        connectedServiceNameARM: $(ServiceConnectionname)
        appType: functionApp # for windows function app
        appName: 'nameofyourfuncapp'
        package: '$(System.ArtifactsDirectory)\nameofyourproj.zip'
        deploymentMethod: 'runFromPackage'

deploymentMethod: ‘runFromPackage’ = instead of running Zip, you will directly run from package

Explanations

The /wwroot folder of your app must look like this

 | - bin
 | - MyFunction
 | - host.json

You can find your folder structure in the portal by:

  • Go to your Function app
  • Go to App Service Editor (Preview)
  • On the top, open the Kudu Console
  • Navigate to site > /wwwroot

A bin folder contains packages and other library files that the function app requires. Specific folder structures required by the function app depend on language: C# compiled (. csproj)

Is your bin file missing? Then you have two options:

  • Manually add the bin folder to your wwwroot in your pipeline
  • Run the function directly from the deployment package file (see code example above)

Also, if you’re runninga .net 8+ function app, make sure that you have the isolated worker model enabled (https://azure.microsoft.com/en-us/updates/ga-azure-functions-supports-net-8-in-the-isolated-worker-model/)

Running from Deployment Package File

You can also choose to run your functions directly from the deployment package file. This method skips the deployment step of copying files from the package to the wwwroot directory of your function app. Instead, the package file is mounted by the Functions runtime, and the contents of the wwwroot directory become read-only.

Zip deployment integrates with this feature, which you can enable by setting the function app setting WEBSITE_RUN_FROM_PACKAGE to a value of 1. For more information, see Run your functions from a deployment package file.

https://learn.microsoft.com/en-us/azure/azure-functions/run-functions-from-deployment-package

When you set WEBSITE_RUN_FROM_PACKAGE = 1, the .zip file is mounted directly, and its contents are not extracted to the wwwroot directory. As a result, wwwroot becomes read-only. But, whether it’s “empty” is misleading; it may have other system files or old content, but it will be accurate to say that it won’t have the active content from the current .zip package. The active content is directly read from the mounted .zip package. (https://stackoverflow.com/questions/76867490/azure-function-clarifications-about-zip-deployment-and-run-from-package-file)

Manually add proj to bin

  • Done using the archive task and specifying the wwwroot folder

Read about ArchiveFiles task

Understand how Zip deployment works

Discussion about how to solve error including archive steps

Fix about the bin folder structure

Look at the Azure pipeline steps for build and publish

Documentation on publish path bin\Release\net8.0\publish\

##[error]TypeError: Cannot read properties of undefined (reading ‘PreDeploymentStep’)

Means that some of the app-settings that are in your app-service cannot be read

Solution:

Either remove some app-settings that actually are not yet supported or find the latest supported version

Error: My ServicePrincipal in Azure DevOps cannot reach the FunctionApp that sits behind a vnet/subnet/private endpoint

Solution:

  • #1 Add network rule to allow SP to deploy function app
## Add release agent to access rules of function
  - task: AzureCLI@2
    inputs:
      azureSubscription: ${{ parameters.serviceConnection }}
      connectedServiceNameARM: ${{ parameters.serviceConnection }}
      scriptType: 'pscore'
      scriptLocation: 'inlineScript' 
      addSpnToEnvironment: true # boolean. Access service principal details in script. Default: false.
      inlineScript: |
        $ip = (Invoke-WebRequest -uri "http://ifconfig.me/ip").Content
        Write-Host Current Agent IP is $ip
        az functionapp config access-restriction add -g '${{ parameters.resourceGroupName }}${{ parameters.environment }}' -n 'plutus-store-func-livediscounts-${{ parameters.environment }}' --rule-name devopsagent --action Allow --ip-address $ip --priority 200
        az functionapp config appsettings set --name 'plutus-store-func-livediscounts-${{ parameters.environment }}' --resource-group '${{ parameters.resourceGroupName }}${{ parameters.environment }}' --settings WEBSITE_RUN_FROM_PACKAGE=1
  • #2 Deploy function app (see previous sections)
  • #3 Remove network rule
## Remove release agent from access rules
  - task: AzureCLI@2
    inputs:
      azureSubscription: ${{ parameters.serviceConnection }}
      connectedServiceNameARM: ${{ parameters.serviceConnection }}
      scriptType: 'pscore'
      scriptLocation: 'inlineScript' 
      addSpnToEnvironment: true # boolean. Access service principal details in script. Default: false.
      inlineScript: |
        $ip = (Invoke-WebRequest -uri "http://ifconfig.me/ip").Content
        Write-Host Current Agent IP is $ip
        az functionapp config access-restriction remove -g '${{ parameters.resourceGroupName }}${{ parameters.environment }}' -n 'plutus-store-func-livediscounts-${{ parameters.environment }}' --rule-name devopsagent

Error: C:\hostedtoolcache\windows\dotnet\sdk\8.0.204\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.ConflictResolution.targets(112,5): error NETSDK1152: Found multiple publish output files with the same relative path:

Solution, append \NameOfYourFunc to arguments output

This way the files will be saved in a bespoke folder

 # Publish build results Func LiveDiscounts
  - task: DotNetCoreCLI@2
    displayName: 'Generate Build Artifacts StoreServe LiveDiscounts Func'
    inputs:
      command: publish
      publishWebProjects: False
      projects: '**\Plutus.StoreServices.LiveDiscounts.Http.csproj'
      arguments: '--configuration  ${{ parameters.buildConfiguration }} --output $(Build.ArtifactStagingDirectory)\LiveDiscounts'
      zipAfterPublish: true

And change the release package

  - task: AzureFunctionApp@1
    displayName: 'Release Live discounts Func'
    inputs:
      azureSubscription: ${{ parameters.serviceConnection }}
      #connectedServiceNameARM: ${{ parameters.serviceConnection }}
      appType: functionApp # for windows function app
      appName: 'plutus-store-func-livediscounts-${{ parameters.environment }}'
      package: '$(System.ArtifactsDirectory)\LiveDiscounts\Plutus.StoreServices.LiveDiscounts.Http.zip'
      deploymentMethod: 'runFromPackage'

Finally add to your csproj file

<PropertyGroup>
  <ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
</PropertyGroup>

Understanding the Issues

  • When you publish multiple Azure Functions projects to the same directory, and these projects have files with the same name (like host.json, local.settings.json, etc.), the .NET SDK cannot resolve which file to keep and which to overwrite, resulting in the NETSDK1152 error.
  • In your specific scenario, both Plutus.StoreServices.LiveDiscounts.Http and Plutus.StoreServices.BulkPrices projects have their own host.json files. When you try to publish these projects to the same output directory, the conflict arises.
  • There has been a breaking change see below
Dotnet
Azure Service Bus
Function App
Microservices
Azure Devops
Recommended from ReadMedium