<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Azure DevOps on Andrew Wilson's Blog</title><link>https://andrewilson.co.uk/tags/azure-devops/</link><description>Recent content in Azure DevOps on Andrew Wilson's Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Wed, 16 Jul 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://andrewilson.co.uk/tags/azure-devops/index.xml" rel="self" type="application/rss+xml"/><item><title>Bicep Tips and Tricks | #1 | Template Versioning and Applying to Azure Resource Tags</title><link>https://andrewilson.co.uk/post/2025/07/bicep-template-versioning-resource-tags-tips-tricks-azure-devops/</link><pubDate>Wed, 16 Jul 2025 00:00:00 +0000</pubDate><guid>https://andrewilson.co.uk/post/2025/07/bicep-template-versioning-resource-tags-tips-tricks-azure-devops/</guid><description>The first of many For those who know me well, starting a bicep tips and tricks series would not be a surprise to them. The moment the Bicep language was introduced, I knew I would be completely obsessed. I love writing bicep templates and even more the clever refinement to make them reusable, configurable, manageable, readable&amp;hellip; I really enjoy sharing my experiences with Bicep, so here it begins on the wider front.</description><content:encoded><![CDATA[<h2 id="the-first-of-many">The first of many</h2>
<p>For those who know me well, starting a bicep tips and tricks series would not be a surprise to them. The moment the Bicep language was introduced, I knew I would be completely obsessed. I love writing bicep templates and even more the clever refinement to make them reusable, configurable, manageable, readable&hellip; I really enjoy sharing my experiences with Bicep, so here it begins on the wider front. From one Bicep nerd to another, I hope you find these tips useful, happy templating!</p>
<h2 id="why-is-versioning-important">Why is versioning important</h2>
<p>Versioning is a fundamental practice in software development that helps us manage change effectively. It provides a structured way to track and communicate updates, ensuring clarity and consistency throughout the development lifecycle.</p>
<ul>
<li>
<p>Why apply a version to your templates?</p>
<p>Applying versioning to templates ensures you can track changes, maintain consistency, and know exactly which version of your infrastructure code was used for any deployment. It supports best practices like binary promotion, simplifies troubleshooting, and helps teams coordinate updates and rollbacks with confidence.</p>
</li>
<li>
<p>Why apply the version as a tag to Azure Resources?</p>
<p>Tagging Azure resources with the template version gives you direct visibility into what was deployed, right from the Azure portal or API. This makes audits, governance, and troubleshooting much easier, as you can instantly see which template version created or updated a resource. It also helps correlate infrastructure changes with application releases and supports compliance requirements for traceability.</p>
</li>
</ul>
<p>ARM templates have a Json property right at the top of the file underneath <code>$schema</code> called <code>contentVersion</code>. By default and if never changed, this will always be <code>1.0.0.0</code>. As part of our versioning practice we can make sure that the templates that we deploy make use of the same version that is applied to other built artifacts. This allows us to track deployments and their respective templates through versions.</p>
<h2 id="how-to-version-templates-using-azure-devops">How to version templates using Azure DevOps</h2>
<p>As part of a YAML CI/CD pipeline I typically have a build stage, with a inner Test and Version ARM Templates Job. The focus of the job is to do two things:</p>
<ol>
<li>Using the az cli - Build the Bicep templates
<ul>
<li>I also include the <a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/linter">Bicep linting file</a>, this means that as the templates are being transpiled into ARM I am also getting any build errors and warnings. I output these as logissues to the Azure DevOps Pipeline for visibility.</li>
</ul>
</li>
<li>Version the built ARM JSON artifacts to support Binary promotion and align with versioning practices.</li>
</ol>
<p>I use the following tasks to complete the tasks:</p>
<ol>
<li><strong>AzureCLI@2</strong> - used to build the Bicep templates (transpile into ARM) and log any warnings or errors.</li>
<li><strong><a href="https://marketplace.visualstudio.com/items?itemName=richardfennellBM.BM-VSTS-Versioning-Task">VersionJSONFile@3</a></strong> - used to version stamp the ARM templates.</li>
<li><strong>CopyFiles@2</strong> - Used to copy the versioned ARM artifacts to the staging directory.</li>
<li><strong>PublishPipelineArtifact@1</strong> - used to publish the ARM artifacts to the pipeline so they can be downloaded in future deployment stages.</li>
</ol>
<p>Here is a sample YAML template:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-YAML" data-lang="YAML"><span style="display:flex;"><span><span style="color:#6272a4"># Yaml</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">task</span>: AzureCLI@2
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Build Bicep Files&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">azureSubscription</span>: <span style="color:#f1fa8c">&#39;xxyyzz&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">scriptType</span>: <span style="color:#f1fa8c">&#39;ps&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">scriptLocation</span>: <span style="color:#f1fa8c">&#39;inlineScript&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">useGlobalConfig</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">powershellerrorActionPreference</span>: <span style="color:#f1fa8c">&#39;continue&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">inlineScript</span>: |<span style="color:#f1fa8c">
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">      # create folder if it doesn&#39;t exist
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">      if (!(Test-Path -Path $(Build.SourcesDirectory)\{YourPath}\IAC\ARMOutput)) {
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">        New-Item -ItemType Directory -Path $(Build.SourcesDirectory)\{YourPath}\IAC\ARMOutput
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">      }
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">      write-host &#34;Build the Bicep file&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">      $output = az bicep build --file $(Build.SourcesDirectory)\{YourPath}\IAC\template.azuredeploy.bicep --outdir $(Build.SourcesDirectory)\{YourPath}\IAC\ARMOutput 2&gt;&amp;1</span>      
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>      write-host &#34;Process the output&#34;
</span></span><span style="display:flex;"><span>      $output | foreach-object {
</span></span><span style="display:flex;"><span>         if ($_ -match &#39;Error&#39;) {
</span></span><span style="display:flex;"><span>            Write-Host &#34;##vso[task.logissue type=error]$_&#34;
</span></span><span style="display:flex;"><span>         } 
</span></span><span style="display:flex;"><span>         if ($_ -match &#39;Warning&#39;) {
</span></span><span style="display:flex;"><span>             Write-Host &#34;##vso[task.logissue type=warning]$_&#34;
</span></span><span style="display:flex;"><span>         }
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">task</span>: VersionJSONFile@3
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Version stamp ARM templates&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">Path</span>: <span style="color:#f1fa8c">&#39;$(Build.SourcesDirectory)\{YourPath}\IAC\ARMOutput&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">recursion</span>: <span style="color:#ff79c6">True</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">VersionNumber</span>: $(Build.BuildNumber) <span style="color:#6272a4"># an example versioning option but can also be options like gitversion</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">useBuildNumberDirectly</span>: <span style="color:#ff79c6">False</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">VersionRegex</span>: \d+\.\d+\.\d+\.\d+
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">versionForJSONFileFormat</span>: <span style="color:#f1fa8c">&#39;{1}.{2}.{3}.{4}&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">FilenamePattern</span>: <span style="color:#f1fa8c">&#39;\w+.json&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">Field</span>: <span style="color:#f1fa8c">&#39;contentVersion&#39;</span> <span style="color:#6272a4"># Versioning is applied to the templates contentVersion field</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">OutputVersion</span>: OutputedVersion
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">task</span>: CopyFiles@2
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Copy ARMOutput to Artifact Staging Directory&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">SourceFolder</span>: <span style="color:#f1fa8c">&#39;$(Build.SourcesDirectory)\{YourPath}\IAC\ARMOutput&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">Contents</span>: <span style="color:#f1fa8c">&#39;**/*.json&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">TargetFolder</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)/templates&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">flattenFolders</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">task</span>: PublishPipelineArtifact@1
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Publish ARMOutput as template build artifact&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">targetPath</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)/templates&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">publishLocation</span>: <span style="color:#f1fa8c">&#39;pipeline&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">artifactName</span>: <span style="color:#f1fa8c">&#39;ARMtemplates&#39;</span>
</span></span></code></pre></div><p>This setup ensures your ARM templates are built, versioned, and published as pipeline artifacts in a repeatable, traceable way. By stamping each template with a version number and making it available as an artifact, you enable binary promotion through environments and make it easy to track exactly which template version was used for each deployment. This improves auditability, supports rollback scenarios, and aligns with best practices for infrastructure as code in CI/CD pipelines.</p>
<h2 id="how-to-apply-the-template-version-as-a-tag-to-azure-resources">How to apply the template version as a tag to Azure Resources</h2>
<p>Versioning our ARM templates is one part of the transparency and traceability. But it would also be nice to be able to see which template version deployed the Azure Resources from the Azure Resources themselves. We can do this by applying a tag onto our resources and tying it back to our ARM <code>contentVersion</code> property field. We can do this by using the <a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-functions-deployment#deployment"><code>deployment()</code></a> function as such <code>deployment().properties.template.contentVersion</code>.</p>
<p>On a resource it would look like the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Bicep" data-lang="Bicep"><span style="display:flex;"><span><span style="color:#6272a4">// Bicep</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">resource</span> <span style="color:#8be9fd;font-style:italic">AppService</span> <span style="color:#f1fa8c">&#39;Microsoft.Web/sites@2024-11-01&#39;</span> = {
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">name</span>: <span style="color:#8be9fd;font-style:italic">appServiceName</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">location</span>: <span style="color:#8be9fd;font-style:italic">location</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">tags</span>: {
</span></span><span style="display:flex;"><span>	...
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">version</span>: <span style="color:#50fa7b">deployment</span>().<span style="color:#8be9fd;font-style:italic">properties</span>.<span style="color:#8be9fd;font-style:italic">template</span>.<span style="color:#8be9fd;font-style:italic">contentVersion</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Applying template versioning and tagging resources with the deployed template’s version brings transparency and traceability to your Azure infrastructure. You’ll always know which version of your code and templates are running in each environment, making troubleshooting and governance much easier. These small practices help teams stay organised, reduce deployment risks, and build confidence in their automation. Happy Bicep-ing!</p>
]]></content:encoded></item><item><title>Automating web.config Environment Transforms in Azure DevOps Pipelines for App Services</title><link>https://andrewilson.co.uk/post/2025/07/webconfig-environment-transform-azure-devops-app-service/</link><pubDate>Wed, 02 Jul 2025 00:00:00 +0000</pubDate><guid>https://andrewilson.co.uk/post/2025/07/webconfig-environment-transform-azure-devops-app-service/</guid><description>Background Modernising legacy applications is always a journey, and recently I tackled moving an existing WCF web service into Azure App Service. If you’ve worked with WCF, you’ll know the web.config file is the nerve centre, handling everything from parameters to connection strings.
But here’s the catch, this service needs to run in multiple environments (Development, Test, and Production) each with its own unique settings. That means multiple config files:</description><content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>Modernising legacy applications is always a journey, and recently I tackled moving an existing WCF web service into Azure App Service. If you’ve worked with WCF, you’ll know the <code>web.config</code> file is the nerve centre, handling everything from parameters to connection strings.</p>
<p>But here’s the catch, this service needs to run in multiple environments (Development, Test, and Production) each with its own unique settings. That means multiple config files:</p>
<ul>
<li>Web.Dev.Config</li>
<li>Web.Test.Config</li>
<li>Web.Production.Config</li>
</ul>
<h2 id="the-challenge">The challenge</h2>
<p>I didn’t want to build and maintain three separate web packages, one for each environment. That’s a recipe for drift and deployment headaches. Instead, I wanted a single build artifact that could be promoted through environments, swapping out only the environment-specific config as needed.</p>
<p>Enter <a href="https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/transform-webconfig?view=aspnetcore-9.0">web.config transformations</a>. With these, you can:</p>
<ul>
<li>Keep a base <code>web.config</code></li>
<li>Layer on environment-specific transforms (<code>Web.Dev.Config</code>, etc.)</li>
</ul>
<p>But when should the transformation happen? There are two main options, each with its own pros and cons:</p>
<ol>
<li>
<p><strong>At build time (using MSBuild):</strong></p>
<ul>
<li>The transformation is applied as part of the build process, so the output artifact already contains the environment-specific configuration.</li>
<li>This approach is simple if you only ever deploy to one environment per build, but it means you need to build a separate artifact for each environment. This breaks the principle of binary promotion, where the same artifact is promoted through Dev, Test, and Production. It also increases the risk of inconsistencies between builds.</li>
</ul>
</li>
<li>
<p><strong>At deployment (using the AzureRMWebAppDeployment@4 Azure DevOps Task):</strong></p>
<ul>
<li>The transformation is applied as part of the deployment process, allowing you to use a single, environment-agnostic build artifact and inject the correct configuration at deploy time.</li>
<li>This is the preferred approach for most modern pipelines, as it enables true binary promotion, reduces build times, and ensures that what you test is exactly what you deploy to production. It also aligns with best practices for repeatable, reliable deployments.</li>
</ul>
</li>
</ol>
<h2 id="solution-deploy-time-transforms">Solution: deploy-time transforms</h2>
<p>Here’s how I set it up:</p>
<h3 id="build-pipeline">Build pipeline</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Yaml" data-lang="Yaml"><span style="display:flex;"><span><span style="color:#6272a4"># YAML</span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">stages</span>:
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Build_Packages&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">job</span>: <span style="color:#f1fa8c">&#39;Build_WCFService&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">checkout</span>: self
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">fetchDepth</span>: <span style="color:#bd93f9">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: VSBuild@1
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Build the WCF Service Solution&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">solution</span>: <span style="color:#f1fa8c">&#39;$(Build.SourcesDirectory)\*.sln&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">msbuildArgs</span>: <span style="color:#f1fa8c">&#39;/p:OutputPath=$(Build.ArtifactStagingDirectory)\WCFService /p:IsTransformWebConfigDisabled=true&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">platform</span>: <span style="color:#f1fa8c">&#39;any cpu&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">vsVersion</span>: <span style="color:#f1fa8c">&#39;latest&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">configuration</span>: <span style="color:#f1fa8c">&#39;Release&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">clean</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: ArchiveFiles@2
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Archive WCF Service Build Output&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">rootFolderOrFile</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)\WCFService\_PublishedWebsites\WCFService.Web&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">includeRootFolder</span>: <span style="color:#ff79c6">false</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">archiveType</span>: <span style="color:#f1fa8c">&#39;zip&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">archiveFile</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)\WCFService\WCFService.zip&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">replaceExistingArchive</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: PublishPipelineArtifact@1
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Publish WCF Service Build Artifact&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">targetPath</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)\WCFService\WCFService.zip&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">publishLocation</span>: <span style="color:#f1fa8c">&#39;pipeline&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">artifactName</span>: <span style="color:#f1fa8c">&#39;WCFService&#39;</span>
</span></span></code></pre></div><h3 id="release-pipeline">Release pipeline</h3>
<p>Each environment (Dev, Test, Production) will get its own stage. The key is to ensure the correct environment name is set so the right transform is applied.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Yaml" data-lang="Yaml"><span style="display:flex;"><span><span style="color:#6272a4"># YAML</span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Dev&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Dev Environment&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">group</span>: <span style="color:#f1fa8c">&#39;Dev&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">dependsOn</span>: <span style="color:#f1fa8c">&#39;Build_Packages&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">condition</span>: succeeded()
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Dev
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">environment</span>: WCFServiceDev
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">strategy</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">runOnce</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">deploy</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">parameters</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">ARMConn</span>: <span style="color:#f1fa8c">&#39;Dev&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Test&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Test Environment&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">group</span>: <span style="color:#f1fa8c">&#39;Test&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">dependsOn</span>: <span style="color:#f1fa8c">&#39;Dev&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">condition</span>: succeeded()
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Test
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">environment</span>: WCFServiceTest
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">strategy</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">runOnce</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">deploy</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">parameters</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">ARMConn</span>: <span style="color:#f1fa8c">&#39;Test&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Production&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Production Environment&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">group</span>: <span style="color:#f1fa8c">&#39;Prod&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">dependsOn</span>: <span style="color:#f1fa8c">&#39;Test&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">condition</span>: succeeded()
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Production
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">environment</span>: WCFServiceProd
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">strategy</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">runOnce</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">deploy</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">parameters</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">ARMConn</span>: <span style="color:#f1fa8c">&#39;Production&#39;</span>
</span></span></code></pre></div><h3 id="deployment-template">Deployment template</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-YAML" data-lang="YAML"><span style="display:flex;"><span><span style="color:#6272a4">#YAML</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">download</span>: current
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Download the wcf Service Web package&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">artifact</span>: WCFService
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">task</span>: AzureRMWebAppDeployment@4
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy WCF Service Web App&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">ConnectionType</span>: <span style="color:#f1fa8c">&#39;AzureRM&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">azureSubscription</span>: ${{ parameters.ARMConn }}
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">appType</span>: <span style="color:#f1fa8c">&#39;webApp&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">WebAppName</span>: <span style="color:#f1fa8c">&#39;$(armOutput.AppServiceName)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">Package</span>: <span style="color:#f1fa8c">&#39;$(Pipeline.Workspace)/WCFService/WCFService.zip&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">enableXmlTransform</span>: <span style="color:#ff79c6">true</span>
</span></span></code></pre></div><p>Here’s the bit that tripped me up: in multi-stage pipelines, the <code>Release.EnvironmentName</code> variable isn’t set by default. Without it, Azure DevOps doesn’t know which transform to apply. The fix? Explicitly set the variable in each stage:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-YAML" data-lang="YAML"><span style="display:flex;"><span><span style="color:#6272a4"># YAML</span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Dev&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Dev Environment&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">name</span>: <span style="color:#f1fa8c">&#39;Release.EnvironmentName&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">value</span>: <span style="color:#f1fa8c">&#39;Dev&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Test&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Test Environment&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">name</span>: <span style="color:#f1fa8c">&#39;Release.EnvironmentName&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">value</span>: <span style="color:#f1fa8c">&#39;Test&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Production&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Production Environment&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">name</span>: <span style="color:#f1fa8c">&#39;Release.EnvironmentName&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">value</span>: <span style="color:#f1fa8c">&#39;Production&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span></code></pre></div><h2 id="troubleshooting-and-tips">Troubleshooting and tips</h2>
<p><strong>Common pitfalls:</strong></p>
<ul>
<li>Ensure your transform files (e.g., <code>Web.Dev.config</code>) are included in your build artifacts. If they’re missing, check your <code>.csproj</code> or project file to confirm they’re marked as <code>Content</code> and set to <code>Copy to Output Directory</code> if needed.</li>
<li>File names are case-sensitive on some systems, double-check spelling and casing.</li>
<li>If transforms aren’t being applied, verify that the <code>Release.EnvironmentName</code> variable is set correctly and matches your transform file naming.</li>
</ul>
<p><strong>Security note:</strong>
Avoid storing secrets or sensitive values directly in your config files. Use Azure Key Vault or pipeline secrets for sensitive data, and reference them via environment variables or pipeline variables where possible.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>With this approach, you get a single, reusable build artifact and environment-specific configuration at deployment, no more juggling multiple packages. It’s a small change that makes your pipeline cleaner and your deployments more reliable.</p>
<p>Hope this helps, happy deploying!</p>
]]></content:encoded></item><item><title>How to Set Up Manual Approval for Azure App Service Slot Swaps in Azure DevOps Pipelines</title><link>https://andrewilson.co.uk/post/2025/06/manual-approval-azure-app-service-slot-swap-azure-devops/</link><pubDate>Fri, 06 Jun 2025 00:00:00 +0000</pubDate><guid>https://andrewilson.co.uk/post/2025/06/manual-approval-azure-app-service-slot-swap-azure-devops/</guid><description>Overview Deploying updates to production environments demands both speed and control. Azure App Service deployment slots, combined with Azure DevOps pipelines, offer a powerful way to manage releases, enabling teams to validate changes in a live-like environment before they go public. However, ensuring that only reviewed and approved changes reach your users is critical for maintaining reliability and compliance.
Azure App Service Slots unlock powerful advantages:
Site Review. Preview and test your site before it goes live, ensuring everything works as expected.</description><content:encoded><![CDATA[<h2 id="overview">Overview</h2>
<p>Deploying updates to production environments demands both speed and control. Azure App Service deployment slots, combined with Azure DevOps pipelines, offer a powerful way to manage releases, enabling teams to validate changes in a live-like environment before they go public. However, ensuring that only reviewed and approved changes reach your users is critical for maintaining reliability and compliance.</p>
<p>Azure App Service Slots unlock powerful advantages:</p>
<ul>
<li><strong>Site Review</strong>. Preview and test your site before it goes live, ensuring everything works as expected.</li>
<li><strong>Warmed-up Instances</strong>. Swap with confidence. Your instances are pre-warmed, so there’s no downtime, seamless traffic redirection, and zero dropped requests.</li>
<li><strong>Instant Rollback</strong>. If something goes wrong after a swap, simply swap back to restore your last known good version in seconds.</li>
</ul>
<p>Best of all, deployment slots come at no extra cost. Check the <a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#azure-app-service-limits">Azure App Service limits</a> to see how many slots your App Service Tier supports.</p>
<h2 id="automatic-vs-manual-slot-swaps">Automatic vs. Manual Slot Swaps</h2>
<p>When it comes to swapping slots, there are two main approaches: <strong>automatic</strong> and <strong>manual</strong>.</p>
<ul>
<li><strong>Automatic swaps</strong> are ideal when you want to quickly promote changes without a review, while still benefiting from pre-warmed instances and instant rollback.</li>
<li><strong>Manual swaps</strong> give you the opportunity to review and verify your site before it goes live, adding an extra layer of confidence.</li>
</ul>
<p>But how do you keep a manual swap process governed and controlled? That’s where Azure DevOps pipelines and approvals come in.</p>
<p>In this article, you’ll learn how to set up a robust, approval-driven slot swap process in Azure DevOps.</p>
<h2 id="defining-azure-resources-with-bicep">Defining Azure Resources with Bicep</h2>
<p>First, we need to define the resources needed in Azure. To do this I am going to use Infrastructure as Code (IaC).
The template shown below creates the following resources:</p>
<ul>
<li>App Service Plan.</li>
<li>App Service linked to the App Service Plan
<ul>
<li>Sub Resource that defines the App Service App Settings</li>
</ul>
</li>
<li>App Service Deployment Slot. <em>Only created for Production environments - this is enforced through a conditional on the resource</em></li>
</ul>
<h3 id="bicep-template-app-service-and-deployment-slot">Bicep Template: App Service and Deployment Slot</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Bicep" data-lang="Bicep"><span style="display:flex;"><span><span style="color:#6272a4">// Bicep</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">/**********************************</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">Bicep</span> <span style="color:#8be9fd;font-style:italic">Template</span>: <span style="color:#8be9fd;font-style:italic">App</span> <span style="color:#8be9fd;font-style:italic">Service</span> <span style="color:#8be9fd;font-style:italic">Deploy</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">Author</span>: <span style="color:#8be9fd;font-style:italic">Andrew</span> <span style="color:#8be9fd;font-style:italic">Wilson</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">***********************************/</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">targetScope</span> = <span style="color:#f1fa8c">&#39;resourceGroup&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ** Parameters **</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ****************</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Org Project Name&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">projectName</span> <span style="color:#8be9fd;font-style:italic">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Application Name sitting within the project&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">applicationName</span> <span style="color:#8be9fd;font-style:italic">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;location name&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">location</span> <span style="color:#8be9fd;font-style:italic">string</span> = <span style="color:#50fa7b">resourceGroup</span>().<span style="color:#8be9fd;font-style:italic">location</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Environment name&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">env</span> <span style="color:#8be9fd;font-style:italic">string</span> = <span style="color:#f1fa8c">&#39;dev&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;App Service Plan sku name&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">appServicePlanSkuName</span> <span style="color:#8be9fd;font-style:italic">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;App Service Plan sku tier&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">appServicePlanSkuTier</span> <span style="color:#8be9fd;font-style:italic">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Name of the deployment slot&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">param</span> <span style="color:#8be9fd;font-style:italic">deploymentSlotName</span> <span style="color:#8be9fd;font-style:italic">string</span> = <span style="color:#f1fa8c">&#39;staging&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ** Variables **</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ***************</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">var</span> <span style="color:#8be9fd;font-style:italic">appServicePlanName</span> <span style="color:#8be9fd;font-style:italic">string</span> = <span style="color:#f1fa8c">&#39;asp-</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">applicationName</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">-</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">env</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">-</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">location</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">var</span> <span style="color:#8be9fd;font-style:italic">appServiceName</span> <span style="color:#8be9fd;font-style:italic">string</span> = <span style="color:#f1fa8c">&#39;app-</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">applicationName</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">-</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">env</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">-</span><span style="color:#f1fa8c">${</span><span style="color:#8be9fd;font-style:italic">location</span><span style="color:#f1fa8c">}</span><span style="color:#f1fa8c">&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ** Resources **</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ***************</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Deploy an Azure App Service Plan&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">resource</span> <span style="color:#8be9fd;font-style:italic">AppServicePlan</span> <span style="color:#f1fa8c">&#39;Microsoft.Web/serverfarms@2024-11-01&#39;</span> = {
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">name</span>: <span style="color:#8be9fd;font-style:italic">appServicePlanName</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">location</span>: <span style="color:#8be9fd;font-style:italic">location</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">tags</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">project</span>: <span style="color:#8be9fd;font-style:italic">projectName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">application</span>: <span style="color:#8be9fd;font-style:italic">applicationName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">environment</span>: <span style="color:#8be9fd;font-style:italic">env</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">version</span>: <span style="color:#50fa7b">deployment</span>().<span style="color:#8be9fd;font-style:italic">properties</span>.<span style="color:#8be9fd;font-style:italic">template</span>.<span style="color:#8be9fd;font-style:italic">contentVersion</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">sku</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">name</span>: <span style="color:#8be9fd;font-style:italic">appServicePlanSkuName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">tier</span>: <span style="color:#8be9fd;font-style:italic">appServicePlanSkuTier</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Deploy an Azure App Service&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">resource</span> <span style="color:#8be9fd;font-style:italic">AppService</span> <span style="color:#f1fa8c">&#39;Microsoft.Web/sites@2024-11-01&#39;</span> = {
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">name</span>: <span style="color:#8be9fd;font-style:italic">appServiceName</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">location</span>: <span style="color:#8be9fd;font-style:italic">location</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">tags</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">project</span>: <span style="color:#8be9fd;font-style:italic">projectName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">application</span>: <span style="color:#8be9fd;font-style:italic">applicationName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">environment</span>: <span style="color:#8be9fd;font-style:italic">env</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">version</span>: <span style="color:#50fa7b">deployment</span>().<span style="color:#8be9fd;font-style:italic">properties</span>.<span style="color:#8be9fd;font-style:italic">template</span>.<span style="color:#8be9fd;font-style:italic">contentVersion</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">properties</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">serverFarmId</span>: <span style="color:#8be9fd;font-style:italic">AppServicePlan</span>.<span style="color:#8be9fd;font-style:italic">id</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">httpsOnly</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">resource</span> <span style="color:#8be9fd;font-style:italic">AppServiceAppSettings</span> <span style="color:#f1fa8c">&#39;config@2024-11-01&#39;</span> = {
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">name</span>: <span style="color:#f1fa8c">&#39;appsettings&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">properties</span>: {
</span></span><span style="display:flex;"><span>      ...
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }  
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Deploy a deployment slot for the App Service&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">resource</span> <span style="color:#8be9fd;font-style:italic">deploymentSlot</span> <span style="color:#f1fa8c">&#39;Microsoft.Web/sites/slots@2024-11-01&#39;</span> = <span style="color:#8be9fd;font-style:italic">if</span> (<span style="color:#8be9fd;font-style:italic">env</span> <span style="color:#ff79c6">==</span> <span style="color:#f1fa8c">&#39;prod&#39;</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">name</span>: <span style="color:#8be9fd;font-style:italic">deploymentSlotName</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">parent</span>: <span style="color:#8be9fd;font-style:italic">AppService</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">location</span>: <span style="color:#8be9fd;font-style:italic">location</span>
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">tags</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">project</span>: <span style="color:#8be9fd;font-style:italic">projectName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">application</span>: <span style="color:#8be9fd;font-style:italic">applicationName</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">environment</span>: <span style="color:#8be9fd;font-style:italic">env</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8be9fd;font-style:italic">version</span>: <span style="color:#50fa7b">deployment</span>().<span style="color:#8be9fd;font-style:italic">properties</span>.<span style="color:#8be9fd;font-style:italic">template</span>.<span style="color:#8be9fd;font-style:italic">contentVersion</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#8be9fd;font-style:italic">properties</span>: { }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// ** Outputs **</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4">// *************</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>@<span style="color:#50fa7b">description</span>(<span style="color:#f1fa8c">&#39;Outputs the App Service Name&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">output</span> <span style="color:#8be9fd;font-style:italic">appServiceName</span> <span style="color:#8be9fd;font-style:italic">string</span> = <span style="color:#8be9fd;font-style:italic">appServiceName</span>
</span></span></code></pre></div><h3 id="important-note-avoiding-autoswap-for-manual-swaps">Important Note: Avoiding AutoSwap for Manual Swaps</h3>
<p>Make sure that the DeploymentSlot resource does not have AutoSwap setup as this will counteract when Manual Swap is used. Such that the following process would be observed:</p>
<ol>
<li>Deploy to Staging slot.</li>
<li>AutoSwap kicks in and swaps Staging with Production.</li>
<li>After Manual Approval, Slots are swapped again</li>
<li>You are now in the originating position with the new site still in Staging and Production with the old.</li>
</ol>
<h2 id="designing-the-azure-devops-pipeline">Designing the Azure DevOps Pipeline</h2>
<p>Next we will setup our Azure DevOps Yaml pipeline. The pipeline’s goal is to orchestrate artifact building and environment deployments while adhering to DevOps best practices, such as <strong>binary promotion</strong>.</p>
<h3 id="pipeline-stages-explained">Pipeline Stages Explained</h3>
<p>The pipeline has the following stages:</p>
<ol>
<li>
<p><strong>Build_Packages</strong>. Build a single set of artifacts that have been tested and versioned. (<em>Best practice of Binary Promotion</em>)</p>
<p>The stage has two jobs to handle both Azure Bicep Templates and the App Service Web Package.</p>
</li>
<li>
<p><strong>Development</strong>. Deploy to the Development Environment using an environment agnostic pipeline template*.</p>
</li>
<li>
<p><strong>Staging</strong>. Deploy to the Staging Environment using an environment agnostic pipeline template*.</p>
</li>
<li>
<p><strong>Production</strong>. Deploy to the Production Environment using an environment agnostic pipeline template*.</p>
</li>
</ol>
<p>*The environment agnostic pipeline template is a reusable template for all environments.</p>
<h3 id="pipeline-yaml-build-and-environment-deployments">Pipeline YAML: Build and Environment Deployments</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-YAML" data-lang="YAML"><span style="display:flex;"><span><span style="color:#6272a4"># Yaml</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Orchestrating Template: Build And Environment Deployments</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">stages</span>:
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Build_Packages&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">job</span>: <span style="color:#f1fa8c">&#39;Test_Build_And_Version_Bicep_Templates&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: PublishPipelineArtifact@1
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Publish ARMOutput as template build artifact&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">targetPath</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)/templates&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">publishLocation</span>: <span style="color:#f1fa8c">&#39;pipeline&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">artifactName</span>: <span style="color:#f1fa8c">&#39;ARMtemplates&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">job</span>: <span style="color:#f1fa8c">&#39;Build_and_Version_AppService&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: PublishPipelineArtifact@1
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Publish App Service Build Artifact&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">targetPath</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)/AppService.zip&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">publishLocation</span>: <span style="color:#f1fa8c">&#39;pipeline&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">artifactName</span>: <span style="color:#f1fa8c">&#39;AppService&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Development&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Dev Environment&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Dev
</span></span><span style="display:flex;"><span>      ...
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Staging&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Staging Environment&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Staging
</span></span><span style="display:flex;"><span>      ...
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Production&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy to Production Environment&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Production
</span></span><span style="display:flex;"><span>      ...
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                ...
</span></span></code></pre></div><p>Once the Build_Packages stage has completed successfully, we now have two pipeline artifacts, ARMtemplates and AppService. In each environment deploy these two artifacts will be downloaded and used alongside environment specific Library Variable Groups to create specific environment deployments.</p>
<h3 id="environment-agnostic-deployment-template">Environment-Agnostic Deployment Template</h3>
<p>The environment agnostic template is shown below and has the following steps:</p>
<ol>
<li><strong>Artifact Downloads</strong>. Downloads the two pipeline artifacts, ARMtemplates and AppService.</li>
<li><strong>Azure Resource Group Deployment</strong>. Using the built ARM template, specific environment library variable group and ARM Connection.</li>
<li><strong>Custom PowerShell Script to Obtain Azure Deployment Outputs</strong>. Deployment output such as appServiceName will be created as job variables ready for future tasks to access.</li>
<li><strong><a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/azure-web-app-v1?view=azure-pipelines">Azure Web App Deploy</a> (Specific for Dev and Staging)</strong>. This task deploys straight to the production slot for non production environments.</li>
<li><strong><a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/azure-web-app-v1?view=azure-pipelines">Azure Web App Deploy</a> (Specific for Prod)</strong>. This task deploys to the staging slot ready for a manual slot swap.</li>
</ol>
<h3 id="pipeline-yaml-template-reusable-environment-agnostic-deployments">Pipeline YAML Template: Reusable Environment-Agnostic Deployments</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-YAML" data-lang="YAML"><span style="display:flex;"><span><span style="color:#6272a4"># Yaml</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Environment Deploy Template: Deploy Azure Resources and Obtain Outputs, Deploy App Service</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">download</span>: current
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Download ARM templates&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">artifact</span>: ARMtemplates
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">download</span>: current
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Download the App Service Web package&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">artifact</span>: AppService
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">task</span>: AzureResourceManagerTemplateDeployment@3
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy App Service ARM template&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">deploymentScope</span>: <span style="color:#f1fa8c">&#39;Resource Group&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">azureResourceManagerConnection</span>: ${{ parameters.ARMConn}}
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">subscriptionId</span>: <span style="color:#f1fa8c">&#39;$(subscriptionId)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">action</span>: <span style="color:#f1fa8c">&#39;Create Or Update Resource Group&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">resourceGroupName</span>: <span style="color:#f1fa8c">&#39;$(resourceGroupName)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">location</span>: <span style="color:#f1fa8c">&#39;$(location)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">templateLocation</span>: <span style="color:#f1fa8c">&#39;Linked artifact&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">csmFile</span>: <span style="color:#f1fa8c">&#39;$(Pipeline.Workspace)/ARMtemplates/AppService.azuredeploy.json&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">deploymentMode</span>: <span style="color:#f1fa8c">&#39;Incremental&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">deploymentOutputs</span>: deploymentOutputs
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">overrideParameters</span>: &gt;-<span style="color:#f1fa8c">
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">        ...</span>        
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">task</span>: PowerShell@2
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">name</span>: armOutput
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Obtain Azure Deployment outputs&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">targetType</span>: <span style="color:#f1fa8c">&#39;inline&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">script</span>: |<span style="color:#f1fa8c">
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">        if (![string]::IsNullOrEmpty( &#39;$(deploymentOutputs)&#39; )) {
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">          $DeploymentOutputs = convertfrom-json &#39;$(deploymentOutputs)&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">          $DeploymentOutputs.PSObject.Properties | ForEach-Object {
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">              $keyname = $_.Name
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">              $value = $_.Value.value
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">              Write-Host &#34;The value of [$keyName] is [$value]&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">              Write-Host &#34;##vso[task.setvariable variable=$keyname;isOutput=true]$value&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">          }
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">        }</span>        
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">task</span>: AzureWebApp@1
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy App Service - Dev and Staging Environment Only&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">condition</span>: ne(variables[&#39;env&#39;], &#39;prod&#39;)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">azureSubscription</span>: ${{ parameters.ARMConn }}
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">appType</span>: <span style="color:#f1fa8c">&#39;webApp&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">appName</span>: <span style="color:#f1fa8c">&#39;$(armOutput.appServiceName)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">package</span>: <span style="color:#f1fa8c">&#39;$(Pipeline.Workspace)/AppService/AppService.zip&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">task</span>: AzureWebApp@1
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Deploy App Service to Staging Slot - Prod Environment Only&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">condition</span>: eq(variables[&#39;env&#39;], &#39;prod&#39;)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">azureSubscription</span>: ${{ parameters.ARMConn }}
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">appType</span>: <span style="color:#f1fa8c">&#39;webApp&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">appName</span>: <span style="color:#f1fa8c">&#39;$(armOutput.appServiceName)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">deployToSlotOrASE</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">resourceGroupName</span>: <span style="color:#f1fa8c">&#39;$(resourceGroupName)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">slotName</span>: <span style="color:#f1fa8c">&#39;$(slotName)&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">package</span>: <span style="color:#f1fa8c">&#39;$(Pipeline.Workspace)/AppService/AppService.zip&#39;</span>
</span></span></code></pre></div><h3 id="accessing-arm-output-variables-across-jobs">Accessing ARM Output Variables Across Jobs</h3>
<p>Given that I will also need to access the appServiceName ARM Output that has been outputted from the armOutput task as a variable in future jobs, I have used the <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/process/set-variables-scripts?view=azure-devops&amp;tabs=bash#set-an-output-variable-for-use-in-future-jobs"><code>Output Variable for use in future jobs</code></a> configuration.</p>
<p>Rather than job only:</p>
<blockquote>
<p>##vso[task.setvariable variable=$keyname]$value</p>
</blockquote>
<p>It uses the IsOutput so we can setup dependencies and access later:</p>
<blockquote>
<p>##vso[task.setvariable variable=$keyname;<code>isOutput=true</code>]$value</p>
</blockquote>
<p><strong>Note</strong>: Accessing a Output Variable is different to a non-output variable:</p>
<blockquote>
<p>Non-output Variable: <code>'$(appServiceName)'</code></p>
<p>Output Variable: <code>'$(armOutput.appServiceName)'</code>, you will need to reference the task name prior to the variable.</p>
</blockquote>
<h3 id="manual-slot-swap-with-azure-devops-approvals">Manual Slot Swap with Azure DevOps Approvals</h3>
<p>At this point, we now have our App Service resources deployed and the Site deployed to the production slots for Dev and Staging, and deployed to the Staging slot for Production.</p>
<p>But now down to the real question, how do I manually swap the slots using some Azure DevOps governance goodness?</p>
<p>To do this we are going to use the Azure DevOps Environment Approvals which will allow specific groups or users to approve the slot swap. Following this we will use the <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/azure-app-service-manage-v0?view=azure-pipelines">AzureAppServiceManage@0</a> task to conduct the slot swap.</p>
<h3 id="setting-up-azure-devops-environments-and-approvals">Setting Up Azure DevOps Environments and Approvals</h3>
<p>To setup an environment and approvals:</p>
<ol>
<li>Sign in to your Azure DevOps organisation and open your project.</li>
<li>Select <strong>Pipelines</strong> &gt; <strong>Environments</strong> &gt; <strong>Create Environment</strong>.</li>
<li>Enter information for the environment, and then select <strong>Create</strong>. (For our purposes leave Resource as None)</li>
<li>In your Environment, Select <strong>Approvals and checks</strong> tab, and then select the + sign to add a new check.</li>
<li>Select <strong>Approvals</strong>, and then select <strong>Next</strong>.</li>
<li>Add users or groups as your designated <strong>Approvers</strong>, and if desired the following
<ol>
<li>Instructions to approvers</li>
<li>Allow approvers to approve their own runs</li>
</ol>
</li>
<li>Make sure to Save.</li>
</ol>
<p>Environments can be assigned to stages and jobs, but not to individual tasks. This means the slot swap task cannot reside within the environment-agnostic deployment template; it must be a separate, follow-on job.</p>
<p>To cater for the slot swap and environment approval, we are going to amend the orchestrating template to add another Job after the deployment Job in the Production deploy stage. This is shown below.</p>
<h3 id="adding-the-slot-swap-job-to-the-pipeline">Adding the Slot Swap Job to the Pipeline</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-YAML" data-lang="YAML"><span style="display:flex;"><span><span style="color:#6272a4"># Yaml</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Orchestrating Template: Build And Environment Deployments</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Production&#39;</span>
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Deploy_to_Production
</span></span><span style="display:flex;"><span>      ...
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">template</span>: EnvironmentDeploy.azurepipelinetemplate.yml
</span></span><span style="display:flex;"><span>                ...
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">deployment</span>: Slot_Swap_to_Production
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">dependsOn</span>: Deploy_to_Production
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">condition</span>: succeeded()
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">environment</span>: ProductionEnv
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">appServiceName</span>: $[ dependencies.Deploy_to_Production.outputs[&#39;Deploy_to_Production.armOutput.appServiceName&#39;] ]
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">strategy</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">runOnce</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">deploy</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>              - <span style="color:#ff79c6">task</span>: AzureAppServiceManage@0
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Swap App Service Staging Slot to Production&#39;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">azureSubscription</span>: <span style="color:#f1fa8c">&#39;XXXYYYZZZ&#39;</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">action</span>: <span style="color:#f1fa8c">&#39;Swap Slots&#39;</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">WebAppName</span>: <span style="color:#f1fa8c">&#39;$(appServiceName)&#39;</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">ResourceGroupName</span>: <span style="color:#f1fa8c">&#39;$(resourceGroupName)&#39;</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">SourceSlot</span>: <span style="color:#f1fa8c">&#39;$(slotName)&#39;</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#ff79c6">SwapWithProduction</span>: <span style="color:#ff79c6">true</span>
</span></span></code></pre></div><p>As can be seen in the amended template above, the Slot_Swap_to_Production job runs after the Deploy_to_Production job and expects it to have succeeded as a condition to running. The Slot_Swap_to_Production job also references the environment ProductionEnv for Approval prior to running.</p>
<p><strong>Note</strong>: The first time the environment is used on the pipeline, the pipeline will ask for permission to use the resource.</p>
<h3 id="referencing-output-variables-in-the-slot-swap-job">Referencing Output Variables in the Slot Swap Job</h3>
<p>The Azure App Service Manage task requires the <code>WebAppName</code>, which is set as an output variable in the environment-agnostic YAML pipeline template. To use this value in the <code>Slot_Swap_to_Production</code> job, define a new variable that references the output variable from the previous job. Once defined, you can use it within the current job as a standard variable (e.g., <code>$(appServiceName)</code>).</p>
<p>The variable reference uses the following syntax:</p>
<blockquote>
<p>[dependencies.<code>JobName</code>.outputs[&rsquo;<code>JobName</code>.<code>TaskName</code>.<code>VariableName</code>]]</p>
</blockquote>
<p>You now have a pipeline that at the point of the slot swap job will wait for an appropriate approval prior to running and swapping your staging slot to production.</p>
<h2 id="summary">Summary</h2>
<p>By combining Azure App Service deployment slots, Infrastructure as Code, and Azure DevOps environment approvals, you can create a robust, auditable deployment pipeline. This approach ensures that production releases are gated by manual approval, reducing risk and enabling rapid rollback if needed.</p>
<p>Ready to take your deployments to the next level? Try setting up manual slot swaps with approvals in your next release pipeline, and let your team experience stress-free, governed production launches.</p>
]]></content:encoded></item><item><title>Automating Semantic Versioning in Azure DevOps CI/CD Pipelines with GitVersion</title><link>https://andrewilson.co.uk/post/2025/05/cicd-and-automatic-semantic-versioning/</link><pubDate>Wed, 07 May 2025 00:00:00 +0000</pubDate><guid>https://andrewilson.co.uk/post/2025/05/cicd-and-automatic-semantic-versioning/</guid><description>Overview Versioning is the unsung hero of software development—often overlooked but absolutely essential. Imagine trying to manage a project without a clear way to track changes, communicate updates, or ensure compatibility. Chaos, right? That’s where versioning steps in, providing structure and clarity.
In this post, I’ll share how I streamline versioning in my projects by combining the power of Semantic Versioning (SemVer) with GitVersion, an automation tool that eliminates the manual effort of version management.</description><content:encoded><![CDATA[<h2 id="overview">Overview</h2>
<p>Versioning is the unsung hero of software development—often overlooked but absolutely essential. Imagine trying to manage a project without a clear way to track changes, communicate updates, or ensure compatibility. Chaos, right? That’s where versioning steps in, providing structure and clarity.</p>
<p>In this post, I’ll share how I streamline versioning in my projects by combining the power of <strong>Semantic Versioning (SemVer)</strong> with <strong>GitVersion</strong>, an automation tool that eliminates the manual effort of version management. Whether you&rsquo;re just beginning your journey or tackling the complexities of feature-rich projects, this post will show you how to automate semantic versioning in <strong>Azure DevOps</strong> for consistency, traceability, and peace of mind.</p>
<h2 id="why-version">Why Version?</h2>
<p>Versioning is a fundamental practice in software development that helps us manage change effectively. It provides a structured way to track and communicate updates, ensuring clarity and consistency throughout the development lifecycle.</p>
<h2 id="what-is-semantic-versioning">What is Semantic Versioning?</h2>
<p>Semantic Versioning (often abbreviated as SemVer) is a versioning scheme that provides a standardised way to communicate the nature of changes in a software release. It uses a three-part version number format: <code>MAJOR.MINOR.PATCH</code>, where each part conveys specific information about the release.</p>
<h3 id="breakdown-of-semantic-versioning">Breakdown of Semantic Versioning:</h3>
<ol>
<li>
<p><strong>MAJOR Version</strong>: Incremented when there are breaking changes that are incompatible with previous versions.</p>
<ul>
<li>Example: <code>1.0.0</code> → <code>2.0.0</code></li>
</ul>
</li>
<li>
<p><strong>MINOR Version</strong>: Incremented when new features are added in a backward-compatible manner.</p>
<ul>
<li>Example: <code>1.0.0</code> → <code>1.1.0</code></li>
</ul>
</li>
<li>
<p><strong>PATCH Version</strong>: Incremented when backward-compatible bug fixes or small improvements are made.</p>
<ul>
<li>Example: <code>1.0.0</code> → <code>1.0.1</code></li>
</ul>
</li>
</ol>
<h3 id="benefits-of-semantic-versioning">Benefits of Semantic Versioning:</h3>
<ul>
<li><strong>Clarity</strong>: It clearly communicates the scope and impact of changes to users and developers.</li>
<li><strong>Predictability</strong>: Helps teams and users understand what to expect from a new release.</li>
<li><strong>Dependency Management</strong>: Makes it easier to specify compatible versions of libraries or APIs.</li>
<li><strong>Automation</strong>: Tools like GitVersion can automate the generation of semantic versions in CI/CD pipelines, ensuring consistency and reducing manual effort.</li>
</ul>
<h2 id="versioning-with-gitversion">Versioning with GitVersion</h2>
<p><a href="https://gitversion.net/docs/">GitVersion</a> is an open source tool that can be used to automate semantic versioning by analysing our Git repository&rsquo;s history. It streamlines the versioning process, ensuring consistency and reducing manual errors.</p>
<p>GitVersion has the following key features:</p>
<ul>
<li><strong>Semantic Versioning (SemVer)</strong>: Automatically calculates version numbers based on Git history, adhering to SemVer principles.</li>
<li><strong>Branching Strategy Support</strong>: Compatible with Continuous Delivery, GitFlow, GitHub Flow, and Mainline development workflows.</li>
<li><strong>Continuous Integration (CI) Friendly</strong>: Integrates seamlessly with CI/CD pipelines, generating version numbers for builds and releases.</li>
<li><strong>Flexible Configuration</strong>: Highly configurable to suit various project needs and versioning schemes.</li>
</ul>
<h3 id="gitversion-configuration">GitVersion Configuration</h3>
<p>GitVersion uses a <a href="https://gitversion.net/docs/reference/configuration">configuration file</a> (GitVersion.yml) to define how version numbers are calculated based on your Git repository&rsquo;s history and branching strategy. This file allows us to customise the behaviour of GitVersion to suit our project&rsquo;s needs.</p>
<h2 id="how-i-branch-and-configure-gitversion-for-projects">How I Branch and Configure GitVersion for Projects</h2>
<p>My projects tend to use the following setup:</p>
<ul>
<li><strong>Continuous Delivery Branch Model</strong>. This model has the following features:
<ul>
<li><strong>Main branch as release-ready</strong>: The <code>main</code> branch is always in a deployable state.</li>
<li><strong>Frequent Deployments</strong>: Changes are deployed to production or staging environments frequently, often automatically.</li>
<li><strong>Short-Lived Feature Branches</strong>: Feature branches are merged into <code>main</code> after review and testing.</li>
<li><strong>Automated Pipelines</strong>: CI/CD pipelines handle building, testing, and deploying changes seamlessly.</li>
</ul>
</li>
<li>SemVer is incremented using the following strategies:
<ul>
<li><strong>TaggedCommit</strong>. This strategy uses Git tags to determine the version. If a commit is tagged with a semantic version (e.g., 1.0.0), GitVersion will use that tag as the base for calculating the next version.</li>
<li><strong>Fallback</strong>. This strategy is used when no other versioning information (e.g., tags or branch-specific rules) is available. It serves as a default versioning mechanism, especially for newly initiated projects.</li>
</ul>
</li>
<li>Two defined branches for GitVersion increments:</li>
</ul>
<table>
<thead>
<tr>
<th><strong>Branch</strong></th>
<th><strong>Increment</strong></th>
<th><strong>When</strong></th>
<th><strong>Prevent Increment If Commit Tagged</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>Main</td>
<td>Major (X.0.0)</td>
<td>Manually using tags for significant breaking changes.</td>
<td>✅ Yes</td>
</tr>
<tr>
<td>Main</td>
<td>Minor (0.X.0)</td>
<td>Automatically on new commits for backward-compatible features.</td>
<td>✅ Yes</td>
</tr>
<tr>
<td>Pull Request</td>
<td>Patch (0.0.X)</td>
<td>Automatically on pull request creation or updates for backward-compatible bug fixes or small improvements.</td>
<td>✅ Yes</td>
</tr>
</tbody>
</table>
<h3 id="gitconfig-setup">GitConfig Setup</h3>
<p>The described GitVersion configuration looks like the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Yaml" data-lang="Yaml"><span style="display:flex;"><span><span style="color:#6272a4">## YAML</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">strategies</span>:
</span></span><span style="display:flex;"><span>- TaggedCommit
</span></span><span style="display:flex;"><span>- Fallback
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">branches</span>:
</span></span><span style="display:flex;"><span>   <span style="color:#ff79c6">main</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">increment</span>: Minor
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">prevent-increment</span>:
</span></span><span style="display:flex;"><span>         <span style="color:#ff79c6">when-current-commit-tagged</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>   <span style="color:#ff79c6">pull-request</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">increment</span>: Patch
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">prevent-increment</span>:
</span></span><span style="display:flex;"><span>         <span style="color:#ff79c6">when-current-commit-tagged</span>: <span style="color:#ff79c6">true</span>
</span></span></code></pre></div><table>
<thead>
<tr>
<th><strong>Configuration</strong></th>
<th><strong>Details</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Strategies</strong></td>
<td>- <code>TaggedCommit</code>: Uses Git tags to determine the version.</td>
</tr>
<tr>
<td></td>
<td>- <code>Fallback</code>: Provides a default versioning mechanism when no other information is available.</td>
</tr>
<tr>
<td><strong>Branches</strong></td>
<td></td>
</tr>
<tr>
<td><code>main</code></td>
<td>- <strong>Increment</strong>: <code>Minor</code> (0.X.0)</td>
</tr>
<tr>
<td></td>
<td>- <strong>Prevent Increment</strong>: Enabled when the current commit is tagged.</td>
</tr>
<tr>
<td><code>pull-request</code></td>
<td>- <strong>Increment</strong>: <code>Patch</code> (0.0.X)</td>
</tr>
<tr>
<td></td>
<td>- <strong>Prevent Increment</strong>: Enabled when the current commit is tagged.</td>
</tr>
</tbody>
</table>
<h3 id="azure-devops-cicd-pipeline-setup">Azure DevOps CI/CD Pipeline Setup</h3>
<p>The CI/CD pipeline would be configured as shown below:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Yaml" data-lang="Yaml"><span style="display:flex;"><span><span style="color:#6272a4">## YAML</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">name</span>: $(Build.DefinitionName)_$(GitVersion.FullSemVer)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">trigger</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">branches</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">include</span>: [ main ] <span style="color:#6272a4"># branch names which will trigger a build</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">pr</span>: <span style="color:#6272a4"># will trigger on PR</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">branches</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">include</span>: [ main ] <span style="color:#6272a4"># branch names which will trigger a build.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">name</span>: Source_Branch_Ref
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">value</span>: $[replace(coalesce(variables[&#39;System.PullRequest.SourceBranch&#39;], variables[&#39;Build.SourceBranch&#39;]), &#39;refs/heads/&#39;, &#39;&#39;)]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">resources</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">repositories</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">repository</span>: Source_Branch
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">type</span>: git
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">name</span>: GitVersionSemVerWithTags
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">ref</span>: <span style="color:#f1fa8c">&#34;$(Source_Branch_Ref)&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">stages</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ff79c6">stage</span>: <span style="color:#f1fa8c">&#39;Build_Packages&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ff79c6">job</span>: <span style="color:#f1fa8c">&#39;Increment_Version&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">condition</span>: and(succeeded(), or(eq(variables[&#39;Build.Reason&#39;], &#39;PullRequest&#39;), and(eq(variables[&#39;Build.SourceBranch&#39;], &#39;refs/heads/main&#39;), ne(variables[&#39;Build.Reason&#39;], &#39;Manual&#39;))))
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">pool</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">vmImage</span>: <span style="color:#f1fa8c">&#39;windows-latest&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">checkout</span>: Source_Branch
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">persistCredentials</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">fetchTags</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">fetchDepth</span>: <span style="color:#bd93f9">0</span> <span style="color:#6272a4"># Ensure we fetch all Git history for Semver</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: gitversion/setup@3
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Get current version of GitVersion&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">versionSpec</span>: <span style="color:#f1fa8c">&#39;6.0.x&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: gitversion/execute@3
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Run GitVersion to generate SEMVER&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">targetPath</span>: <span style="color:#f1fa8c">&#39;$(Build.SourcesDirectory)\&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">            useConfigFile: true
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">            configFilePath: &#39;</span>$(Build.SourcesDirectory)\GitVersion.yml&#39;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: PowerShell@2
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Increment the Version using Git Tag&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">targetType</span>: <span style="color:#f1fa8c">&#39;inline&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">script</span>: |<span style="color:#f1fa8c">
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">              cd &#39;$(Build.SourcesDirectory)&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">              git config --global user.email &#34;$(Build.RequestedForEmail)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">              git config --global user.name &#34;$(Build.RequestedFor)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">              git tag -a &#34;$(GitVersion.MajorMinorPatch)&#34; -m &#34;Released by $(Build.RequestedFor)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">              git push origin tag &#34;$(GitVersion.MajorMinorPatch)&#34;</span>              
</span></span></code></pre></div><h4 id="key-steps-in-the-pipeline">Key Steps in the Pipeline</h4>
<ol>
<li><strong>Pipeline Name</strong>: Combines the build definition name with the full semantic version: <code>$(Build.DefinitionName)_$(GitVersion.FullSemVer)</code>.</li>
<li><strong>Trigger</strong>: Builds are triggered on changes to the <code>main</code> branch.</li>
<li><strong>Pull Request Trigger</strong>: Builds are triggered on pull requests targeting the <code>main</code> branch.</li>
<li><strong>Variables</strong>: Defines <code>Source_Branch_Ref</code> to extract the source branch reference for the build or pull request.</li>
<li><strong>Resources</strong>: Dynamically sets the branch reference for the Git repository (<code>Source_Branch</code>) to <code>$(Source_Branch_Ref)</code>.</li>
<li><strong>Job: Increment_Version</strong>: Executes if the build succeeds and is triggered by a pull request or the <code>main</code> branch but not part of a <code>Manual Trigger</code>.</li>
<li><strong>Checkout</strong>: Checks out the <code>Source_Branch</code> repository with full Git history and tags for versioning.</li>
<li><strong>GitVersion Setup</strong>: Installs GitVersion (<code>6.0.x</code>) to calculate semantic versions.</li>
<li><strong>GitVersion Execute</strong>: Runs GitVersion using the <code>GitVersion.yml</code> configuration file to generate the semantic version.</li>
<li><strong>PowerShell Script</strong>:
<ul>
<li>Configures Git by setting the user email and name to match the build requester.</li>
<li>Creates or updates a Git tag with the calculated version (<code>$(GitVersion.MajorMinorPatch)</code>).</li>
<li>Pushes the tag to the remote repository, ensuring the version is recorded.</li>
</ul>
</li>
</ol>
<blockquote>
<p>⚠️ <strong>Important Note</strong><br>
The <code>Source_Branch_Ref</code> variable and resource in this pipeline are configured as shown above because pull requests (PRs) create their own Git branches which are a merger of the source branch and main. When tags are created during the pipeline execution, they are placed on the PR branch by default, not the source branch where GitVersion calculates automatic increments.<br>
By setting up the <code>Source_Branch_Ref</code> variable and dynamically referencing the source branch in the <code>resources</code> section, the tag is placed on the source branch instead of the PR branch. This ensures that version increments are correctly applied to the source branch, maintaining accurate semantic versioning.<br>
The PR branch should still be used for building and validating solution artifacts.</p>
</blockquote>
<blockquote>
<p>⚠️ <strong>Important Pipeline Permissions</strong><br>
The Azure DevOps [Project] Build Service must have the following Repository Permissions:</p>
<ul>
<li><strong>Contribute</strong>: Permission to push changes to the repository.</li>
<li><strong>Create Tag</strong>: Permission to create and update tags in the repository.</li>
</ul>
</blockquote>
<h3 id="using-gitversion-to-version-artifacts">Using GitVersion to Version Artifacts</h3>
<p>Versioning artifacts involves two phases:</p>
<ol>
<li><strong>Generate</strong> a semantic version to use. This can be achieved by leveraging the GitVersion task.</li>
<li><strong>Applying</strong> the sematic version to artifacts. This can be achieved by using a task such as <a href="https://marketplace.visualstudio.com/items?itemName=richardfennellBM.BM-VSTS-Versioning-Task">VersionJSONFile@3</a>.</li>
</ol>
<p>As an example, when working with ARM templates in Azure, it&rsquo;s important to version your artifacts for traceability and consistency. Using GitVersion in your Azure DevOps pipeline, you can  generate a semantic version and apply it to the <code>contentVersion</code> field of your ARM template. This guarantees that each deployment is uniquely identifiable.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Yaml" data-lang="Yaml"><span style="display:flex;"><span><span style="color:#6272a4">## YAML</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ff79c6">job</span>: <span style="color:#f1fa8c">&#39;Test_and_VersionBicepTemplates&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">pool</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">vmImage</span>: <span style="color:#f1fa8c">&#39;windows-latest&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">checkout</span>: self
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">path</span>: <span style="color:#f1fa8c">&#39;./s/selfBranch/&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">persistCredentials</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">fetchTags</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">fetchDepth</span>: <span style="color:#bd93f9">0</span> <span style="color:#6272a4"># Ensure we fetch all Git history for Semver</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">checkout</span>: Source_Branch
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">path</span>: <span style="color:#f1fa8c">&#39;./s/versionBranch/&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">persistCredentials</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">fetchTags</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">fetchDepth</span>: <span style="color:#bd93f9">0</span> <span style="color:#6272a4"># Ensure we fetch all Git history for Semver</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#6272a4"># GitVersion task is needed in each job where the variables are referenced</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: gitversion/setup@3
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Get current version of GitVersion&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">versionSpec</span>: <span style="color:#f1fa8c">&#39;6.0.x&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: gitversion/execute@3
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Run GitVersion to generate SEMVER&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">targetPath</span>: <span style="color:#f1fa8c">&#39;$(System.DefaultWorkingDirectory)/versionBranch/&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">useConfigFile</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">configFilePath</span>: <span style="color:#f1fa8c">&#39;$(System.DefaultWorkingDirectory)/versionBranch/GitVersion.yml&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: BicepInstall@0
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">version</span>: <span style="color:#bd93f9">0.35.1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: BicepBuild@0
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">process</span>: <span style="color:#f1fa8c">&#34;single&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">sourceFile</span>: <span style="color:#f1fa8c">&#39;$(Build.SourcesDirectory)\selfBranch\Deployment\azuredeploy.bicep&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">stdout</span>: <span style="color:#ff79c6">false</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">outputFile</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)\ARMOutput\azuredeploy.json&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: VersionJSONFile@3
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Version stamp ARM templates&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">Path</span>: <span style="color:#f1fa8c">&#39;$(Build.ArtifactStagingDirectory)\ARMOutput&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">recursion</span>: <span style="color:#ff79c6">true</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">VersionNumber</span>: <span style="color:#f1fa8c">&#39;$(GitVersion.AssemblySemFileVer)&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">useBuildNumberDirectly</span>: <span style="color:#ff79c6">False</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">VersionRegex</span>: <span style="color:#f1fa8c">&#39;\d+\.\d+\.\d+\.\d+&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">versionForJSONFileFormat</span>: <span style="color:#f1fa8c">&#39;{1}.{2}.{3}.{4}&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">FilenamePattern</span>: <span style="color:#f1fa8c">&#39;\w+.json&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">Field</span>: <span style="color:#f1fa8c">&#39;contentVersion&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">OutputVersion</span>: <span style="color:#f1fa8c">&#39;OutputedVersion&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        - <span style="color:#ff79c6">task</span>: PublishPipelineArtifact@1
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">displayName</span>: <span style="color:#f1fa8c">&#39;Publish Versioned Solution Templates build artefact&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">targetPath</span>: <span style="color:#f1fa8c">&#34;$(Build.ArtifactStagingDirectory)/ARMOutput&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">publishLocation</span>: <span style="color:#f1fa8c">&#34;pipeline&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">artifactName</span>: <span style="color:#f1fa8c">&#34;ARM-Templates&#34;</span>
</span></span></code></pre></div><h4 id="key-steps-in-the-pipeline-1">Key Steps in the Pipeline</h4>
<ol>
<li>
<p><strong>Checkout Repositories</strong></p>
<ul>
<li>The pipeline checks out two branches:
<ul>
<li><strong>Self Branch</strong>: Contains the Bicep files that will be compiled into ARM templates.</li>
<li><strong>Source Branch</strong>: Used for versioning and fetching Git history.</li>
</ul>
</li>
<li>Full Git history and tags are fetched (<code>fetchTags: true</code>, <code>fetchDepth: 0</code>) to ensure accurate semantic version calculation.</li>
</ul>
</li>
<li>
<p><strong>Generate Semantic Version with GitVersion</strong></p>
<ul>
<li><strong>GitVersion Setup</strong>: The <code>gitversion/setup@3</code> task installs GitVersion (<code>6.0.x</code>).</li>
<li><strong>GitVersion Execution</strong>: The <code>gitversion/execute@3</code> task calculates the semantic version based on the repository&rsquo;s history and configuration (<code>GitVersion.yml</code>).
<ul>
<li>The version is exposed as <a href="https://gitversion.net/docs/reference/variables">pipeline variables</a>, such as <code>$(GitVersion.AssemblySemFileVer)</code>.</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>Build ARM Templates</strong></p>
<ul>
<li>The <code>BicepBuild</code> task compiles the Bicep file (<code>azuredeploy.bicep</code>) into an ARM template (<code>azuredeploy.json</code>).</li>
<li>The compiled ARM template is stored in the <code>$(Build.ArtifactStagingDirectory)/ARMOutput</code> directory.</li>
</ul>
</li>
<li>
<p><strong>Version Stamp the ARM Template</strong></p>
<ul>
<li>The <code>VersionJSONFile</code> task updates the <code>contentVersion</code> field in the ARM template (<code>azuredeploy.json</code>) with the semantic version generated by GitVersion.
<ul>
<li><strong>Inputs</strong>:
<ul>
<li><code>VersionNumber</code>: Uses <code>$(GitVersion.AssemblySemFileVer)</code> (Provides a 4-digit version format (<code>MAJOR.MINOR.PATCH.0</code>), which is ideal for ARM template versioning e.g., <code>1.2.3.0</code>).</li>
<li><code>Field</code>: Specifies the <code>contentVersion</code> field in the ARM template to be updated.</li>
<li><code>VersionRegex</code>: Ensures only valid version formats (<code>\d+\.\d+\.\d+\.\d+</code>) are replaced.</li>
</ul>
</li>
<li>This step ensures that the ARM template is uniquely versioned for traceability.</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>Publish the Versioned ARM Template</strong></p>
<ul>
<li>The <code>PublishPipelineArtifact</code> task publishes the versioned ARM template as a pipeline artifact.
<ul>
<li>The artifact is stored under the name <code>ARM-Templates</code> and can be used in subsequent deployment stages.</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="summary">Summary</h2>
<p>Automating semantic versioning in Azure DevOps CI/CD pipelines with GitVersion is a game-changer for maintaining consistency, traceability, and efficiency in your development workflow. By leveraging tools like GitVersion, you can eliminate the manual effort of version management, ensure accurate versioning across branches, and streamline the deployment of versioned artifacts like ARM templates.</p>
<p>Whether you&rsquo;re managing simple projects or complex, feature-rich solutions, adopting semantic versioning practices ensures that your team and stakeholders have a clear understanding of changes, compatibility, and release impact. With the strategies and pipeline configurations shared in this post, you’re now equipped to implement a robust versioning system that aligns with industry best practices.</p>
<p>If you found this post useful, consider sharing it with your team or network to help others streamline their versioning workflows. Happy automating!</p>
]]></content:encoded></item><item><title>webpack | Build Time Environment Variables With Azure DevOps Yaml CI/CD</title><link>https://andrewilson.co.uk/post/2024/07/webpack-build-time-environment-variables/</link><pubDate>Thu, 25 Jul 2024 00:00:00 +0000</pubDate><guid>https://andrewilson.co.uk/post/2024/07/webpack-build-time-environment-variables/</guid><description>Problem Space One of my recent projects has involved the use of a static module bundler called webpack to bundle a typescript site so I can serve static content from a Static Web App in Azure.
For a while now the site content has not deviated between environments [ dev / test / prod ] and therefore we have simply built and bundled the site for deployment.
Recent changes however have required that there be some deviation between environments, at which point the question raised, at what point should this deviation be set or retrieved?</description><content:encoded><![CDATA[<h2 id="problem-space">Problem Space</h2>
<p>One of my recent projects has involved the use of a static module bundler called <a href="https://webpack.js.org/">webpack</a> to bundle a typescript site so I can serve static content from a <a href="https://learn.microsoft.com/en-us/azure/static-web-apps/overview">Static Web App</a> in Azure.</p>
<p>For a while now the site content has not deviated between environments [ dev / test / prod ] and therefore we have simply built and bundled the site for deployment.</p>
<p>Recent changes however have required that there be some deviation between environments, at which point the question raised, at what point should this deviation be set or retrieved?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#ff79c6">const</span> someConfigurationSetup <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">&#34;Static but deviates between environments&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">const</span> someConfigurationSetupPartTwo <span style="color:#ff79c6">=</span> <span style="color:#f1fa8c">&#34;Static but deviates between environments&#34;</span>;
</span></span></code></pre></div><p>Due to the site having a backing Azure Function serving its API calls, I had two available options:</p>
<ol>
<li>Is the deviation something that should be served at runtime?
<blockquote>
<p>Points to the Azure Function APIs.</p>
</blockquote>
</li>
<li>Is the deviation something that should be baked in at build time?
<blockquote>
<p>Points to the webpack bundler.</p>
</blockquote>
</li>
</ol>
<p>The deviation in this case does not change throughout the running of the site and also has no security constraints preventing the deviation from being served as static content. My choice therefore proceeded with the second option: webpack bundler.</p>
<p>But how do I achieve this with the webpack bundler? I have a Azure DevOps CI/CD Yaml pipeline that builds and packages the site using npm and the webpack bundler ready for the environment deployments. What is the most appropriate method that will fit in with my current methods?</p>
<h2 id="solution">Solution</h2>
<p>After some digging, I came across the <a href="https://webpack.js.org/plugins/environment-plugin/">webpack EnvironmentPlugin</a>. The use of the Environment Plugin allows me to specify environment variables within my typescript that will then be resolved by the webpack bundler at build time.</p>
<p>This changes the typescript shown above to the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#ff79c6">const</span> someConfigurationSetup <span style="color:#ff79c6">=</span> process.env.CONFIG_SETUP_ONE;
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">const</span> someConfigurationSetupPartTwo <span style="color:#ff79c6">=</span> process.env.CONFIG_SETUP_TWO;
</span></span></code></pre></div><p>By default if <code>process.env.VARIABLE</code> is not defined by your webpack.config.js, you will receive a ReferenceError: <code>process is not defined</code>.</p>
<p>Defining the environment variables in the webpack.config.js is done within the plugins section such as the following:</p>
<pre tabindex="0"><code class="language-webpack" data-lang="webpack">module.exports = async (env, options) =&gt; {

  const config = [
    {
      devtool: ...
      entry: {
        ...
      },
      resolve: {
        ...
      },
      module: {
        rules: [
          ...
        ],
      },
      plugins: [
        new DefinePlugin({
          &#39;process.env.CONFIG_SETUP_ONE&#39;: JSON.stringify(process.env.CONFIG_SETUP_ONE),
          &#39;process.env.CONFIG_SETUP_TWO&#39;: JSON.stringify(process.env.CONFIG_SETUP_TWO),
        }),
        ...
      ],
    },
    {
	...
    }
  ];

  return config;
};
</code></pre><p>With the plugin and variables defined, the webpack bundler will now resolve my variables with any environment variables defined through methods such as:</p>
<ol>
<li>Setting through PowerShell (<em>environment variable setup will differ per scripting language</em>):
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-PowerShell" data-lang="PowerShell"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$env:CONFIG_SETUP_ONE</span> = <span style="color:#f1fa8c">&#34;&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$env:CONFIG_SETUP_TWO</span> = <span style="color:#f1fa8c">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>npm run build
</span></span></code></pre></div></li>
<li>Setting through <a href="https://webpack.js.org/plugins/environment-plugin/#dotenvplugin">.env file</a></li>
</ol>
<p>If no environment variables are found, the variable resolve will result in one of the following:</p>
<ol>
<li><code>undefined</code> for variables that must be provided during bundling.</li>
<li><code>null</code> if they are optional.</li>
</ol>
<p>In my case I would like the variables to be resolved within my Azure DevOps Yaml CI/CD Pipeline for the respective environment.</p>
<p>Thankfully, not much work was required here from my part. I am already making use of <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/library/variable-groups?view=azure-devops&amp;tabs=yaml">Library Variable Groups</a> within my pipeline for the respective environment stages.
In DevOps pipelines, variables are made available to scripts and tasks through environment variables such as the one I configured: CONFIG_SETUP_ONE.
This has resulted in no change to the existing yaml task for the resolve to work:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>      - <span style="color:#ff79c6">job</span>: <span style="color:#f1fa8c">&#39;Build_Test_Solution&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">pool</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">vmImage</span>: windows-latest
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">variables</span>:
</span></span><span style="display:flex;"><span>          - <span style="color:#ff79c6">group</span>: Dev <span style="color:#6272a4"># Holds the Environment Variables such as CONFIG_SETUP_ONE, CONFIG_SETUP_TWO</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>          - <span style="color:#ff79c6">task</span>: Npm@1
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">displayName</span>: npm install
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#ff79c6">workingDir</span>: <span style="color:#f1fa8c">&#39;$(System.DefaultWorkingDirectory)/Project&#39;</span>
</span></span><span style="display:flex;"><span>              <span style="color:#ff79c6">verbose</span>: <span style="color:#ff79c6">false</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>          - <span style="color:#ff79c6">task</span>: Npm@1
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">displayName</span>: npm run build for test environment
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">inputs</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#ff79c6">command</span>: custom
</span></span><span style="display:flex;"><span>              <span style="color:#ff79c6">workingDir</span>: <span style="color:#f1fa8c">&#39;$(System.DefaultWorkingDirectory)/Project&#39;</span>
</span></span><span style="display:flex;"><span>              <span style="color:#ff79c6">verbose</span>: <span style="color:#ff79c6">false</span>
</span></span><span style="display:flex;"><span>              <span style="color:#ff79c6">customCommand</span>: run build
</span></span></code></pre></div><p>Hope this helps, and have fun.</p>
]]></content:encoded></item><item><title>Azure DevOps Pipeline | Git Shallow Fetch</title><link>https://andrewilson.co.uk/post/2023/01/azure-devops-pipeline-shallow-fetch/</link><pubDate>Mon, 09 Jan 2023 00:00:00 +0000</pubDate><guid>https://andrewilson.co.uk/post/2023/01/azure-devops-pipeline-shallow-fetch/</guid><description>Problem Space I have been recently working on building a new Yaml pipeline in Azure DevOps and wished to use the GitVersion Task, however, upon running the pipeline the task failed with the following error:
ERROR [../../.. ..:..:..:..] An unexpected error occurred: System.NullReferenceException: Object reference not set to an instance of an object. After some digging and a conversation with one of my colleagues, it turns out there has been a change on Azure DevOps pipelines.</description><content:encoded><![CDATA[<h2 id="problem-space">Problem Space</h2>
<p>I have been recently working on building a new Yaml pipeline in Azure DevOps and wished to use the <a href="https://marketplace.visualstudio.com/items?itemName=gittools.gittools">GitVersion Task</a>, however, upon running the pipeline the task failed with the following error:</p>
<pre tabindex="0"><code>ERROR [../../.. ..:..:..:..] An unexpected error occurred:
System.NullReferenceException: Object reference not set to an instance of an object.
</code></pre><p>After some digging and a conversation with one of my colleagues, it turns out there has been a change on Azure DevOps pipelines. By default now when a pipeline is created, the &lsquo;<em>Get Sources</em>&rsquo; <strong>Shallow fetch</strong> setting is enabled and set to a depth of 1.</p>
<p>The <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/pipeline-options-for-git?view=azure-devops&amp;tabs=yaml#shallow-fetch">Shallow fetch</a> setting if enabled will limit how far back in source history to download. Effectively using the following git command <code>git fetch --depth=n</code>.</p>
<p>This in turn causes the error seen above as the GitVersion task requires the whole repo branching model to calculate the SEMVER version.</p>
<h2 id="switching-off-shallow-fetch">Switching off Shallow Fetch</h2>
<p>To switch off <strong>Shallow Fetch</strong> if using a Yaml pipeline:</p>
<ol>
<li>Edit your Pipeline.</li>
<li>Selecting the ellipsis button, select Triggers.</li>
<li>Navigate to the Yaml tab, and select Get Sources.</li>
<li>There is a setting near the very bottom called <em>Shallow fetch</em>, switch the setting off and save.</li>
</ol>
]]></content:encoded></item></channel></rss>