<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Martin</title>
    <description>The latest articles on DEV Community by Martin (@lakkimartin).</description>
    <link>https://dev.to/lakkimartin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F803086%2Fdf04131b-1385-4699-bc27-3037882a0a9d.png</url>
      <title>DEV Community: Martin</title>
      <link>https://dev.to/lakkimartin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lakkimartin"/>
    <language>en</language>
    <item>
      <title>Azure Private DNS</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Thu, 09 Nov 2023 00:06:48 +0000</pubDate>
      <link>https://dev.to/lakkimartin/azure-private-dns-25n9</link>
      <guid>https://dev.to/lakkimartin/azure-private-dns-25n9</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;DNS, DNS, DNS, It's all about DNS. I've circled around this topic for a while, and it's been a common discussion point with many of the clients I have worked with. &lt;/p&gt;

&lt;p&gt;DNS is absolutely critical in any environment and if not done correctly it can cause a heap of issues. I am going to talk through some of the options for resolving DNS specifically for private endpoints in Azure and more importantly what Microsoft recommends and, in my opinion, works well. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private Endpoints&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Firstly, let's start with private endpoints. What are they? A private endpoint in Azure is simply a network interface card (NIC) with a private IP address in your virtual network that you can attach to common Azure PaaS services like Azure Storage or Azure SQL that allows you to communicate with that service privately and securely over an internal RFC1918 IP address. Prior to private endpoints these services typically used a service endpoint where in order to connect, your traffic traversed the Microsoft backbone network instead of your virtual network which of course many customers deemed not secure as your traffic is leaving your private network. Service endpoints are still available today. They are by no means unsecure but don't meet the security controls in some heavily regulated environments. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Private DNS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Azure Private DNS is simply a DNS service in Azure that cannot be resolved publicly. It allows you to use your own custom DNS names rather than the DNS names provided by Azure which are public. Azure Private DNS is the fundamental building block of Private Endpoints. Private Endpoints heavily rely on a private DNS zone to resolve a DNS name against the private IP address of the Network Interface Card (NIC) I mentioned previously. For example, if you want to resolve a private endpoint for Azure blob storage then you would create a zone named &lt;code&gt;privatelink.blob.core.windows.net&lt;/code&gt;. In order to resolve DNS, the zone must be linked to a virtual network where the originating DNS request is coming from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure Private DNS Resolution Options
&lt;/h2&gt;

&lt;p&gt;Getting into the meat here. There are several options to resolve private DNS in Azure and from on-premises. I will list the most common: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On-Premises to Azure&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DNS Virtual Machine in Azure with a forwarder configured on the on-premises DNS Server (sends all DNS to Azure)&lt;/li&gt;
&lt;li&gt;DNS Virtual Machine in Azure with a conditional forwarder configured on the on-premises DNS Server (sends only specific zones to Azure)&lt;/li&gt;
&lt;li&gt;Azure DNS Private Resolver (DNS Forwarder PaaS Service)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Azure Only &amp;amp; Hybrid Workloads&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure Private DNS Zone hosted in your hub virtual network and linked to any spoke virtual networks (Recommended for Azure only workloads)&lt;/li&gt;
&lt;li&gt;Azure Private DNS Zone hosted in your spoke network and linked to your spoke virtual network (Not Recommended)&lt;/li&gt;
&lt;li&gt;Azure DNS Private Resolver Centralised (Recommended for Hybrid Workloads)&lt;/li&gt;
&lt;li&gt;Azure DNS Private Resolver Distributed (Recommended for Hybrid Workloads)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common approach I see which &lt;code&gt;is not recommended&lt;/code&gt; is where a DNS zone is deployed for each spoke or application landing zone deployment which means you might end up with tens or hundreds of zones just for one service for example &lt;code&gt;privatelink.blob.core.windows.net&lt;/code&gt;. This is hard to manage and not necessary. You want to manage your DNS centrally similar to managing a hub firewall. You wouldn't deploy an NVA for each spoke network so why do it for DNS. &lt;/p&gt;

&lt;h2&gt;
  
  
  Azure DNS Private Resolver
&lt;/h2&gt;

&lt;p&gt;Before I dive into the architecture options, DNS Private Resolver is a fairly recent service introduced by Microsoft. It's essentially a DNS Forwarder offered as a PaaS service. This eliminates the need for deploying a DNS forwarded virtual machine. One less IaaS service to patch &amp;amp; maintain. The networking enthusiasts were pretty happy to see this introduced.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8v86fclrrlssqmbvsxk3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8v86fclrrlssqmbvsxk3.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I thought I would include a cost comparison. The IaaS option includes a 2 VM high availability setup. The DNS resolver comes out slightly more expensive however this doesn't factor in the operational overhead with managing a virtual machines, backup costs etc. It's fully a PaaS service so you really don't have to do any maintenance apart from updating your forwarding rulesets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distributed vs Centralised
&lt;/h2&gt;

&lt;p&gt;With DNS Private Resolver, there are 2 architectures that can be adopted. &lt;/p&gt;

&lt;p&gt;Centralised DNS has the Azure DNS Private Resolver as the custom DNS server on all spoke networks meaning all DNS is resolved within the Hub network. In this scenario the DNS Resolver Forwarding Ruleset is linked to the hub virtual network only where the rules are defined. &lt;/p&gt;

&lt;p&gt;Distributed DNS uses the default Azure DNS server for all spoke networks with the DNS Resolver Forwarding Ruleset linked to the spoke networks to allow for the resolution to private DNS zones. By default, all DNS is resolved in the spoke virtual network unless it's a private DNS zone. &lt;/p&gt;

&lt;p&gt;The first option has the Private DNS Zones linked to the hub only. The second option you can opt in for either. You can link the DNS Zones to each spoke network or link the DNS zone only to the hub but ensure you have forwarding rules in your linked ruleset to resolve private DNS zones. Either approach works. Microsoft references both in 2 different articles with no definitive answer on which is best. Both seem to have the same operational overhead.&lt;/p&gt;

&lt;p&gt;In my example I have opted for a direct DNS zone link to the spoke virtual network. &lt;/p&gt;

&lt;h2&gt;
  
  
  Distributed DNS Architecture
&lt;/h2&gt;

&lt;p&gt;In a typical scenario where you might have a hub network, you can deploy a single zone and link it each spoke virtual network. Now your only managing 1 zone for that service across your whole Azure estate.  &lt;/p&gt;

&lt;p&gt;Let's break this down visually: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrdg1hrnf0ejks5vlwti.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrdg1hrnf0ejks5vlwti.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;(1, 2 &amp;amp; 3) All on-premises DNS queries that need to resolve an Azure Private DNS name (i.e. Private Endpoints) are forwarded to the Azure Private Resolver using conditional forwarding on Active Directory DNS. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(4) Any DNS queries that need to resolve Azure Private DNS from the spoke network are resolved through the private DNS zone link using the default Azure DNS on the virtual network.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(5,6) Any DNS queries from the spoke virtual network that need to resolve DNS on-premises are forwarded to the on-premises DNS server through a forwarding rule set on the Private DNS resolver that uses the outbound endpoint. This is done through a DNS forwarding virtual network link. Essentially a conditional forwarder for any on-prem DNS queries. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This configuration is referred to as a &lt;code&gt;distributed&lt;/code&gt;DNS architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Centralised DNS Architecture
&lt;/h2&gt;

&lt;p&gt;In a centralised DNS architecture, the spoke virtual networks use the Private DNS Resolver inbound endpoint as a custom DNS server.&lt;/p&gt;

&lt;p&gt;The key difference here is that the Private DNS Zone is linked to the hub instead of the spokes and the forwarding ruleset is linked to the hub virtual network and not the spoke, so the forwarding rules are managed centrally. &lt;/p&gt;

&lt;p&gt;Here is the different visually: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwsmi6lg41bt1j6p5ur8t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwsmi6lg41bt1j6p5ur8t.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;(1, 2 &amp;amp; 3) All on-premises DNS queries that need to resolve an Azure Private DNS name (i.e. Private Endpoints) are forwarded to the Azure Private Resolver using conditional forwarding on Active Directory DNS. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(4) Any DNS queries that need to resolve Azure Private DNS from the spoke network are resolved through the Private DNS resolver in the Hub which queries Azure DNS to resolve the Private DNS zone linked to the hub.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(5) Any DNS queries from the spoke virtual network that need to resolve DNS on-premises are forwarded to the on-premises DNS server through a forwarding rule set on the Private DNS resolver that uses the outbound endpoint. This is done through a DNS forwarding virtual network link. Essentially a conditional forwarder for any on-prem DNS queries. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no right or wrong here with both architectures. It depends on how you want to manage this. Some organisations have to inspect all traffic centrally through an NVA so this centralised option would be best suited. &lt;/p&gt;

&lt;p&gt;It's worth noting that if you have Azure Firewall, it can act as a DNS proxy if you adopt the centralised DNS architecture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhm98hl4xuqq5p7gesvk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhm98hl4xuqq5p7gesvk.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this scenario you can forward your DNS requests to the Azure firewall IP. Now you get the advantage of traffic inspection and DNS caching. More on the topic &lt;a href="https://learn.microsoft.com/en-us/azure/firewall/dns-settings" rel="noopener noreferrer"&gt;here &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your workloads are only in Azure and you don't have the need for a DNS forwarder then create your private DNS zones in the Hub and link them to all of your spoke networks. &lt;/p&gt;

&lt;p&gt;Key takeaway is not to duplicate DNS zones by keeping them centralised.&lt;/p&gt;

&lt;p&gt;Hope this was useful and let me know in the comments if you would like to see more on this topic. I am looking to get this setup automated using Terraform as a reference architecture.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cloud</category>
      <category>dns</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Running Azure Functions on Azure Container Apps</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Mon, 07 Aug 2023 05:41:52 +0000</pubDate>
      <link>https://dev.to/lakkimartin/running-azure-functions-on-azure-container-apps-2jlg</link>
      <guid>https://dev.to/lakkimartin/running-azure-functions-on-azure-container-apps-2jlg</guid>
      <description>&lt;h2&gt;
  
  
  Service Overview
&lt;/h2&gt;

&lt;p&gt;Microsoft recently announced the ability to run Azure Functions on Azure Container Apps (in preview). This is a big improvement over the current serverless option for Azure Functions as it allows us to leverage the benefits of microservices and the features of Azure Container Apps like DAPR and KEDA. &lt;/p&gt;

&lt;p&gt;Behind the scenes, KEDA is used as the event driven auto scaler for the Functions. You still need to spin up an Azure Function for managing your functions however they execute within the containers (compute environment). This is also a lot more cost efficient as you can use the Azure Container Apps consumption plan and only pay for what you use. &lt;/p&gt;

&lt;p&gt;These are the triggers currently supported whilst in preview that allow dynamic scaling from zero instances: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Http&lt;/li&gt;
&lt;li&gt;Azure Storage Queue&lt;/li&gt;
&lt;li&gt;Azure Service Bus&lt;/li&gt;
&lt;li&gt;Azure Event Hubs&lt;/li&gt;
&lt;li&gt;Kafka Trigger&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm certain that more triggers will be available down the line as KEDA itself has a huge library of various trigger types. &lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;I will run through a quick demo which is also available on the Microsoft docs. It's a PowerShell Function that responds to an http request (API, Get) and returns a response if the request is successful. Pretty simple but you can develop whatever function you desire in all the languages currently supported by Azure Functions (.net, Java etc). I also ran some load Tests using Azure Load Testing (JMeter) to see how the scaling performs in real life so you can check that out at the end of the post. &lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30nqtnhabmm0hq6cc8hy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30nqtnhabmm0hq6cc8hy.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what we will be getting up and running today. The exception being that we won't be using virtual network integration or private endpoints just to speed things up but in a production environment you definitely want to enforce all the network traffic to remain within a virtual network in Azure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-Requisites
&lt;/h3&gt;

&lt;p&gt;You will need the following if you want to run this yourself&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure &lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=windows%2Cportal%2Cv2%2Cbash&amp;amp;pivots=programming-language-powershell#v2" rel="noopener noreferrer"&gt;Functions Core Tools&lt;/a&gt; version 4.x.&lt;/li&gt;
&lt;li&gt;Install the &lt;a href="https://dotnet.microsoft.com/en-us/download" rel="noopener noreferrer"&gt;.NET 6 SDK&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli" rel="noopener noreferrer"&gt;Azure CLI&lt;/a&gt; version 2.4 or a later version.&lt;/li&gt;
&lt;li&gt;An Azure &lt;a href="https://learn.microsoft.com/en-us/azure/container-registry/container-registry-get-started-azure-cli" rel="noopener noreferrer"&gt;Container Registry&lt;/a&gt; to push the container images to.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Set up local development environment
&lt;/h3&gt;

&lt;p&gt;Before we deploy anything to Azure, let's get our Function set up locally and testing it using Postman (you can also just use a web browser). &lt;/p&gt;

&lt;p&gt;1: Initiate the Functions project&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--worker-runtime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;powershell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--docker&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The docker feature flag will generate a .Dockerfile for the project which we will use later to wrap up the code into a Docker image and push it up to the container registry. &lt;/p&gt;

&lt;p&gt;2: Let's add a Function to our project.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HttpTriggerContainerApp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HTTP trigger"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--authlevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;anonymous&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;3: Once that's done, we need to test it locally to make sure our Function executes properly before we containerise it. To start the Function locally run: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="w"&gt;  


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zndl6vhrl0pij96spf2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zndl6vhrl0pij96spf2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see the URL for the Function in your terminal: &lt;a href="http://localhost:7071/api/HttpTriggerContainerApp" rel="noopener noreferrer"&gt;http://localhost:7071/api/HttpTriggerContainerApp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4: Now let's call our API using by suppliying a query parameter in the Http Get request. If you haven't got Postman you can just call pass the parameter via your web browser: &lt;/p&gt;

&lt;p&gt;&lt;a href="http://localhost:7071/api/HttpTriggerContainerApp?name=Functions" rel="noopener noreferrer"&gt;http://localhost:7071/api/HttpTriggerContainerApp?name=Functions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8fbm6opkerx8yazhzve.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8fbm6opkerx8yazhzve.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we should have a response if the call was successful: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw01cz4jb1bieosedm25y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw01cz4jb1bieosedm25y.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we know the Function executes using the Http trigger locally we can move on to build our image and getting the same Function executing in Azure Container Apps. &lt;/p&gt;

&lt;h3&gt;
  
  
  Building our Docker image
&lt;/h3&gt;

&lt;p&gt;Our Docker image was automatically generated when we created our Functions project so all we need to do is build it, tag it and push it up to our container registry. &lt;/p&gt;

&lt;p&gt;You will need Docker to do this step. I am using Podman which is an alternative for building OCI images but the commands are the same. Just replace &lt;code&gt;podman&lt;/code&gt; with &lt;code&gt;docker&lt;/code&gt;in the command line. Make sure you cd into the root directory of the project.&lt;/p&gt;

&lt;p&gt;You will need to replace the &lt;code&gt;acr_name&lt;/code&gt; with your registry in Azure. Make sure you have the admin credentials enabled on the registry. You can do this via the CLI &lt;code&gt;az acr update -n &amp;lt;acr_name&amp;gt; --admin-enabled true&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;1: Build the image: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

podman build --tag &amp;lt;acr_name&amp;gt;.azurecr.io/azurefunctionsimage:v1.0.0 .


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;2: Run the image locally and test it again via podman or your web browser: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

podman run -p 8080:80 -it &amp;lt;acr_name&amp;gt;.azurecr.io/azurefunctionsimage:v1.0.0


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;3: Our container is now listening on port 8080 so here is the new URL to test with: &lt;a href="http://localhost:8080/api/HttpTriggerContainerApp?name=Functions" rel="noopener noreferrer"&gt;http://localhost:8080/api/HttpTriggerContainerApp?name=Functions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foekk2i4er1bgs1h8938b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foekk2i4er1bgs1h8938b.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we know our image works when running in a container we can push it our container registry. &lt;/p&gt;

&lt;p&gt;4: Log in to your Azure Container Registry: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

podman login &amp;lt;acr_name&amp;gt;.azurecr.io -u &amp;lt;acr_username&amp;gt; -p &amp;lt;acr_password)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;5: Push the image to the container registry by running the following: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

podman push &amp;lt;acr_name&amp;gt;.azurecr.io/azurefunctionsimage:v1.0.0


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now that our image is ready we can proceed to setting up our Azure environment and deploying our Function App on to Azure Contaiener Apps. &lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Function on to Azure Container Apps
&lt;/h3&gt;

&lt;p&gt;1: Run the below commands to register the required namespaces and to upgrade the Container Apps CLI extension: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;extension&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;containerapp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--upgrade&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-y&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Web&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.App&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.OperationalInsights&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;2: Create the resource group for the resources in Azure: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AzureFunctionsDemo-rg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;australiaeast&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;3: Create the Container App environment: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;containerapp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;azure-functions-demo-cae&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--resource-group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AzureFunctionsDemo-rg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;australiaeast&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;4: Create the storage account required for the Function Apps. This is used to store all of the Function code used in the runtime: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auefunctionsdemo001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;australiaeast&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--resource-group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AzureFunctionsDemo-rg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--sku&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Standard_LRS&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;4: Create the Function App. You will notice here we need to provide the container registry details. This is what allows Azure Function to deploy our Function on to the Container App Environment: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;functionapp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;azure-functions-demo-func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--storage-account&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auefunctionsdemo001&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--environment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;azure-functions-demo-cae&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--resource-group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AzureFunctionsDemo-rg&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--functions-version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--runtime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;powershell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;acr_name&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;azurecr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;io&lt;/span&gt;&lt;span class="nx"&gt;/azurefunctionsimage:v1.0.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--registry-server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;acr_name&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;azurecr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;io&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--registry-username&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;acr_username&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--registry-password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;acr_password&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In a production scenario you would use a managed identity to authenticate to the container registry. &lt;/p&gt;

&lt;p&gt;5: Let's retrieve the endpoint of our function so we can call this and test that we get the expected response from the API: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;functionapp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--resource-group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AzureFunctionsDemo-rg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;azure-functions-demo-func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--function-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HttpTriggerContainerApp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;invokeUrlTemplate&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0suf4w78b301jr8tc41t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0suf4w78b301jr8tc41t.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have the endpoint, we can test it the same way we did when we deployed it locally. I am using Postman to do this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0thbwven45kklcgfvbk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0thbwven45kklcgfvbk.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can now see that we get the exact same response with the Function running on Azure container apps!&lt;/p&gt;

&lt;p&gt;This was a relatively simple setup that shows the concept of running Functions on Container Apps. The benefits are that we can use the feature of Container Apps on our Functions like Keda and DAPR. Additionally, it seems it's cheaper to run Azure Container Apps with vNet integration that running a standard App Service Plan or Azure Functions premium which are required in order to integrate into your own virtual network.&lt;/p&gt;

&lt;p&gt;P.S You can configure your Container App Environment in your own virtual network allowing your Function app to communicate privately over RFC1918 to any other Azure services you have or private endpoints. &lt;/p&gt;

</description>
      <category>devops</category>
      <category>azure</category>
    </item>
    <item>
      <title>New Azure Service Retirement Workbook from Microsoft</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Wed, 14 Jun 2023 12:37:50 +0000</pubDate>
      <link>https://dev.to/lakkimartin/new-azure-service-retirement-workbook-from-microsoft-3i1g</link>
      <guid>https://dev.to/lakkimartin/new-azure-service-retirement-workbook-from-microsoft-3i1g</guid>
      <description>&lt;p&gt;Microsoft regularly releases new features and services in Azure. This is the evergreen nature of the cloud however they also retire services which is where infrastructure lifecycle management comes into play. Some new releases never make it out of preview and some services become legacy and get retired. A good example is a lot of the Azure classic services which were introduced when Azure was called Windows Azure. &lt;/p&gt;

&lt;p&gt;Microsoft has released a neat little workbook that provides a central view of service retirements across Azure and specifically which resources in your subscriptions may be impacted to make it easier for customers to track these retirements. Previously we had to rely on Azure Advisor and checking the Microsoft update blog. &lt;/p&gt;

&lt;p&gt;Here is how you can view this in your Azure tenant. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt; - Navigate to the Azure workbooks &lt;a href="https://portal.azure.com/#view/Microsoft_Azure_Expert/AdvisorMenuBlade/~/workbooks"&gt;Gallery&lt;/a&gt; and select the Service Retirement workbook&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pb_TX3Pg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/92amcojh8zl5ppbvuy6h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pb_TX3Pg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/92amcojh8zl5ppbvuy6h.png" alt="Image description" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you should be able to see a list of services that may be impacted across your subscriptions. I have also enabled auto refresh and set the frequency to daily. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2EmRv6vE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r9t3zhb8bqz5rt5tj5iw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2EmRv6vE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r9t3zhb8bqz5rt5tj5iw.png" alt="Image description" width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the list above you can see I only have one resource that is affected and this is my public IP addresses with a basic SKU. Something Microsoft is retiring so only standard SKU will be available going forward. &lt;/p&gt;

&lt;p&gt;You can also filter the view to show all retirements rather than a list of just retirements where resources in your subscriptions may be impacted. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M6AQ9bMS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hybop1fs9disn9h28t28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M6AQ9bMS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hybop1fs9disn9h28t28.png" alt="Image description" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, you can open a up a link for each retirement which will take you to the Microsoft Docs so you can find more details and remediation guidance. &lt;/p&gt;

&lt;p&gt;Do note that the workbook is in public preview so not all services that are retiring are listed. Microsoft will be expanding on these in the future and hopefully this will make it to GA.&lt;/p&gt;

&lt;p&gt;Pretty cool. Hopefully something you can leverage in your environment.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Convert ARM Template to Bicep using VS Code Extension</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Tue, 11 Apr 2023 23:49:25 +0000</pubDate>
      <link>https://dev.to/lakkimartin/convert-arm-template-to-bicep-using-vs-code-extension-25k7</link>
      <guid>https://dev.to/lakkimartin/convert-arm-template-to-bicep-using-vs-code-extension-25k7</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Microsoft has just announced a new enhancement to their Bicep extension for Visual Studio code and I thought I would share this with the wider community. &lt;/p&gt;

&lt;p&gt;Many of us have been working with ARM templates for a long time and it is evident that Microsoft is now pushing for Bicep adoption which is leaving a lot of engineers having to refactor existing ARM template deployments to Bicep. &lt;/p&gt;

&lt;p&gt;Microsoft has now made this a lot easier with the new feature on the Bicep Visual Studio Code extension that allows you to paste ARM templates (JSON) into the Bicep format. &lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-Requisites
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Make sure you have the Bicep &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep" rel="noopener noreferrer"&gt;extension &lt;/a&gt;installed in Visual Studio Code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayg0348jlnuf6enj7mym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayg0348jlnuf6enj7mym.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Some JSON code that you want to convert. We will be using an Azure Container Registry for this demo. &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Converting your template.
&lt;/h2&gt;

&lt;p&gt;In this example we will convert the following template for an Azure Container Registry: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"contentVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"registries_example_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"defaultValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"String"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"variables"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Microsoft.ContainerRegistry/registries"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"apiVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-01-01-preview"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[parameters('registries_example_name')]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"australiaeast"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Basic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Basic"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"adminUserEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"policies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"quarantinePolicy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"disabled"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"trustPolicy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Notary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"disabled"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"retentionPolicy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"disabled"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"exportPolicy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"enabled"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"azureADAuthenticationAsArmPolicy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"enabled"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"softDeletePolicy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"retentionDays"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"disabled"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"encryption"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"disabled"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"dataEndpointEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"publicNetworkAccess"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"networkRuleBypassOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AzureServices"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"zoneRedundancy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Disabled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"anonymousPullEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;1: Create a new file with the Bicep extension e.g. &lt;code&gt;test.bicep&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;2: Right click on the file in Visual Studio Code and select "Paste JSON as Bicep" &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi94ndzfk6lxmo491qedk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi94ndzfk6lxmo491qedk.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3: Your template should now be converted to Bicep! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzn7umxcub17kcxk47wq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzn7umxcub17kcxk47wq.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the Bicep code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="err"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;registries_example_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'example'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;registries_example_name_resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Microsoft.ContainerRegistry/registries@&lt;/span&gt;&lt;span class="mi"&gt;2023-01-01&lt;/span&gt;&lt;span class="err"&gt;-preview'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;registries_example_name&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;location:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'australiaeast'&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;sku:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Basic'&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;tier:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Basic'&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;properties:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;adminUserEnabled:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;policies:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;quarantinePolicy:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;status:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'disabled'&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;trustPolicy:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Notary'&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;status:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'disabled'&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;retentionPolicy:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;days:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;status:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'disabled'&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;exportPolicy:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;status:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'enabled'&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;azureADAuthenticationAsArmPolicy:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;status:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'enabled'&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;softDeletePolicy:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;retentionDays:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;status:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'disabled'&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;encryption:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;status:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'disabled'&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;dataEndpointEnabled:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;publicNetworkAccess:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Enabled'&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;networkRuleBypassOptions:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'AzureServices'&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;zoneRedundancy:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Disabled'&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;anonymousPullEnabled:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;



&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you would expect some manual enhancements may be required at times, but this seems to make the process much easier.&lt;/p&gt;

&lt;p&gt;You can also paste single lines or parts of code from JSON and it will also automatically convert them which is a nice feature. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>devops</category>
      <category>json</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Adding linting, validation and what-if to Bicep templates in Azure DevOps</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Sun, 26 Feb 2023 22:10:57 +0000</pubDate>
      <link>https://dev.to/lakkimartin/adding-linting-validation-and-what-if-to-bicep-templates-in-azure-devops-2cjp</link>
      <guid>https://dev.to/lakkimartin/adding-linting-validation-and-what-if-to-bicep-templates-in-azure-devops-2cjp</guid>
      <description>&lt;p&gt;We are picking up from my last post where I walked through automating Bicep template deployments through Azure DevOps so if you haven't caught up here is a &lt;a href="https://dev.to/lakkimartin/deploying-bicep-template-using-azure-devops-4m53"&gt;link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pipeline we set up in the previous post ran a single job that deployed our Azure resource using Bicep. However, in a production scenario we want to run some validation checks to ensure our new code won't breaking anything. Stages in Azure DevOps pipelines comprise of jobs and tasks e.g. running a script and you can think of them as containers for grouping different jobs. &lt;/p&gt;

&lt;p&gt;Let's look at the new stages that we will be adding to our pipeline. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpmxchm4zzigrn6py3hlo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpmxchm4zzigrn6py3hlo.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's understand each of the stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lint: Use the Bicep linter to verify that the Bicep file is well formed and doesn't contain any obvious errors.&lt;/li&gt;
&lt;li&gt;Validate: Use the Azure Resource Manager preflight validation process to check for problems that might occur when you deploy.&lt;/li&gt;
&lt;li&gt;Preview: Use the what-if command to validate the list of changes that will be applied against your Azure environment. This allows people to review and approve the changes before deployment. &lt;/li&gt;
&lt;li&gt;Deploy: Submit your deployment to Resource Manager and wait for it to finish pending the approval gate.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Converting our pipeline to a multistage pipeline
&lt;/h2&gt;

&lt;p&gt;Since we want to run multiple stages we need to add some additional code to our YAML template. This is going to look something like this: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LintCode&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate&lt;/span&gt;
  &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ValidateBicepTemplate&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Preview&lt;/span&gt;
  &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Preview&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
  &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Preview&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;By default stages run in parallel if you have enough agents however, we want to run these stages sequentially so I have added a &lt;code&gt;dependsOn:&lt;/code&gt; to achieve this meaning that each stage can only run once the previous stage completes successfully. &lt;/p&gt;

&lt;p&gt;This is the basic structure and in the next few sections we will build up each stage of the pipeline. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Linting
&lt;/h2&gt;

&lt;p&gt;Linting validation will check for things like unused parameters, unused variables, interpolation, secure parameters and more. The first thing we need to do is add the lint stage to our pipeline: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Bicep files&lt;/span&gt;


&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImageName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest'&lt;/span&gt;

  &lt;span class="na"&gt;azureServiceConnection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;devops-poc3-deploy-spi'&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;australiaeast'&lt;/span&gt;
  &lt;span class="na"&gt;templateFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bicep/azure_resource_group/template.bicep'&lt;/span&gt;
  &lt;span class="na"&gt;templateParameterFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bicep/azure_resource_group/parameters.json'&lt;/span&gt;

&lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(vmImageName)&lt;/span&gt;

&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LintCode&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint code&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;az bicep build --file $(Build.SourcesDirectory)/$(templateFile)&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LintBicepCode&lt;/span&gt;
        &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Bicep linter&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Azure pipeline doesn't treat lint warnings as problems, so we need to add a configuration file to the repository where our bicep file is located. Copy and paste the below and name the file &lt;code&gt;bicepconfig.json&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5siico73pegbn6mudxh2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5siico73pegbn6mudxh2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"analyzers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"core"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"verbose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"adminusername-should-not-be-literal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"max-outputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"max-params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"max-resources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"max-variables"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"no-hardcoded-env-urls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"no-unnecessary-dependson"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"no-unused-params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"no-unused-vars"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"outputs-should-not-contain-secrets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"prefer-interpolation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"secure-parameter-default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"simplify-interpolation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"protect-commandtoexecute-secrets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"use-stable-vm-image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Our pipeline will now fail if any of the linting flags warnings. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Bicep template validation
&lt;/h2&gt;

&lt;p&gt;We can use the Azure Resource Manager Deployment Task from the previous post to also run a validation against the ARM API to check for anything that may cause the deployment to fail e.g. reserved name for a storage account. &lt;/p&gt;

&lt;p&gt;Let's get this stage added. Copy and paste the below straight after the lint stage (careful of the YAML indentation): &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate&lt;/span&gt;
  &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ValidateBicepTemplate&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AzureResourceManagerTemplateDeployment@3&lt;/span&gt;
      &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;deploymentScope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Subscription'&lt;/span&gt;
        &lt;span class="na"&gt;azureResourceManagerConnection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(azureServiceConnection)'&lt;/span&gt;
        &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(location)'&lt;/span&gt;
        &lt;span class="na"&gt;templateLocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Linked&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;artifact'&lt;/span&gt;
        &lt;span class="na"&gt;csmFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.SourcesDirectory)/$(templateFile)'&lt;/span&gt;
        &lt;span class="na"&gt;csmParametersFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.SourcesDirectory)/$(templateParameterFile)'&lt;/span&gt;
        &lt;span class="na"&gt;deploymentMode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Validation'&lt;/span&gt;  


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Adding What-If (Preview) analysis.
&lt;/h2&gt;

&lt;p&gt;Before deploying the changes, we can run a what-if operation that will give you a preview of the template will change. This is a really cool feature and very similar to Terraform Plan where you can see exactly the number of changes the Bicep template will perform (if any). &lt;/p&gt;

&lt;p&gt;Let's get this stage added. Copy and paste the below straight after the validation stage: &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Preview&lt;/span&gt;
  &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Preview&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AzureCLI@2&lt;/span&gt;
      &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;azureSubscription&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(azureServiceConnection)'&lt;/span&gt;
        &lt;span class="na"&gt;scriptType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bash'&lt;/span&gt;
        &lt;span class="na"&gt;scriptLocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inlineScript'&lt;/span&gt;
        &lt;span class="na"&gt;inlineScript&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;az deployment sub what-if \&lt;/span&gt;
            &lt;span class="s"&gt;--template-file $(Build.SourcesDirectory)/$(templateFile) \&lt;/span&gt;
            &lt;span class="s"&gt;--parameters '$(Build.SourcesDirectory)/$(templateParameterFile)' \&lt;/span&gt;
            &lt;span class="s"&gt;--location '$(location)'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Run the pipeline and select the preview stage to look at the results: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3kf4e1f3jncr9f2h929.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3kf4e1f3jncr9f2h929.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk03vp3ginnzgxoh2w9ch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk03vp3ginnzgxoh2w9ch.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that the what-if is showing that one resource will be created and in this instance it's the resource group we defined in the previous post. &lt;/p&gt;

&lt;h2&gt;
  
  
  Add approval gates
&lt;/h2&gt;

&lt;p&gt;The final step is to add an approval gate to our deploy stage which will deploy the Bicep template with the desired infrastructure. &lt;/p&gt;

&lt;p&gt;Paste the following code after the preview stage: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BicepEnvironment&lt;/span&gt;
      &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runOnce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;checkout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;self&lt;/span&gt;

            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AzureResourceManagerTemplateDeployment@3&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DeployBicepTemplate&lt;/span&gt;
              &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;deploymentScope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Subscription'&lt;/span&gt;
                &lt;span class="na"&gt;azureResourceManagerConnection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(azureServiceConnection)'&lt;/span&gt;
                &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Update&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Resource&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Group'&lt;/span&gt;
                &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(location)'&lt;/span&gt;
                &lt;span class="na"&gt;templateLocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Linked&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;artifact'&lt;/span&gt;
                &lt;span class="na"&gt;csmFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.SourcesDirectory)/$(templateFile)'&lt;/span&gt;
                &lt;span class="na"&gt;csmParametersFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.SourcesDirectory)/$(templateParameterFile)'&lt;/span&gt;
                &lt;span class="na"&gt;deploymentMode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Incremental'&lt;/span&gt;
                &lt;span class="na"&gt;deploymentName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DeployPipelineTemplate'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You will notice that in order to use an environment for approvals we have added a deployment which is structured slightly differently from a regular job. An environment is simply a place where you deploy your solution and allows you to add approval gates and more. &lt;/p&gt;

&lt;p&gt;Let's run the pipeline and see the approval in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft2nt413dauu00bozxfg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft2nt413dauu00bozxfg1.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because we are using an environment, we need to allow our pipeline permissions to deploy using the environment: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0bfaf6bv2odlbjt7t4f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0bfaf6bv2odlbjt7t4f.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once we allow our pipeline access to the environment in Azure DevOps we can go ahead and approve the changes: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c58jqyg63hamdujkzfr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c58jqyg63hamdujkzfr.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is our Resource Group deployed in Azure: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1tj998201fd64cl35j7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1tj998201fd64cl35j7.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the complete multi-stage pipeline. You can also find the code on my GitHub page &lt;a href="https://github.com/lakovmartin/azure/blob/main/arm/resource_group/azure-pipelines-full.yaml" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Bicep files&lt;/span&gt;


&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImageName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest'&lt;/span&gt;

  &lt;span class="na"&gt;azureServiceConnection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;devops-poc3-deploy-spi'&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;australiaeast'&lt;/span&gt;
  &lt;span class="na"&gt;templateFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bicep/azure_resource_group/template.bicep'&lt;/span&gt;
  &lt;span class="na"&gt;templateParameterFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bicep/azure_resource_group/parameters.json'&lt;/span&gt;

&lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(vmImageName)&lt;/span&gt;

&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LintCode&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint code&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;az bicep build --file $(Build.SourcesDirectory)/$(templateFile)&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LintBicepCode&lt;/span&gt;
        &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Bicep linter&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate&lt;/span&gt;
  &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ValidateBicepTemplate&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AzureResourceManagerTemplateDeployment@3&lt;/span&gt;
      &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;deploymentScope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Subscription'&lt;/span&gt;
        &lt;span class="na"&gt;azureResourceManagerConnection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(azureServiceConnection)'&lt;/span&gt;
        &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(location)'&lt;/span&gt;
        &lt;span class="na"&gt;templateLocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Linked&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;artifact'&lt;/span&gt;
        &lt;span class="na"&gt;csmFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.SourcesDirectory)/$(templateFile)'&lt;/span&gt;
        &lt;span class="na"&gt;csmParametersFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.SourcesDirectory)/$(templateParameterFile)'&lt;/span&gt;
        &lt;span class="na"&gt;deploymentMode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Validation'&lt;/span&gt;        

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Preview&lt;/span&gt;
  &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Preview&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AzureCLI@2&lt;/span&gt;
      &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;azureSubscription&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(azureServiceConnection)'&lt;/span&gt;
        &lt;span class="na"&gt;scriptType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bash'&lt;/span&gt;
        &lt;span class="na"&gt;scriptLocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inlineScript'&lt;/span&gt;
        &lt;span class="na"&gt;inlineScript&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;az deployment sub what-if \&lt;/span&gt;
            &lt;span class="s"&gt;--template-file $(Build.SourcesDirectory)/$(templateFile) \&lt;/span&gt;
            &lt;span class="s"&gt;--parameters '$(Build.SourcesDirectory)/$(templateParameterFile)' \&lt;/span&gt;
            &lt;span class="s"&gt;--location '$(location)'&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BicepEnvironment&lt;/span&gt;
      &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runOnce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;checkout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;self&lt;/span&gt;

            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AzureResourceManagerTemplateDeployment@3&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DeployBicepTemplate&lt;/span&gt;
              &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;deploymentScope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Subscription'&lt;/span&gt;
                &lt;span class="na"&gt;azureResourceManagerConnection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(azureServiceConnection)'&lt;/span&gt;
                &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Update&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Resource&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Group'&lt;/span&gt;
                &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(location)'&lt;/span&gt;
                &lt;span class="na"&gt;templateLocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Linked&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;artifact'&lt;/span&gt;
                &lt;span class="na"&gt;csmFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.SourcesDirectory)/$(templateFile)'&lt;/span&gt;
                &lt;span class="na"&gt;csmParametersFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.SourcesDirectory)/$(templateParameterFile)'&lt;/span&gt;
                &lt;span class="na"&gt;deploymentMode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Incremental'&lt;/span&gt;
                &lt;span class="na"&gt;deploymentName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DeployPipelineTemplate'&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In a production environment you would also want to add some smoke testing or pester testing which can be easily achieved with PowerShell. &lt;/p&gt;

&lt;p&gt;Hope you enjoyed the read.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>testing</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Deploying Bicep Template using Azure DevOps</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Thu, 23 Feb 2023 02:39:05 +0000</pubDate>
      <link>https://dev.to/lakkimartin/deploying-bicep-template-using-azure-devops-4m53</link>
      <guid>https://dev.to/lakkimartin/deploying-bicep-template-using-azure-devops-4m53</guid>
      <description>&lt;p&gt;In this short post we are going to look at automating the deployment of a Bicep template using Azure DevOps. Additionally, we are going to look at how you can convert an ARM template to Bicep. &lt;/p&gt;

&lt;p&gt;You can find all the files used in this post on my GitHub &lt;a href="https://github.com/lakovmartin/azure/tree/main/arm/resource_group" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Azure Bicep is almost an abstraction of ARM (JSON) templates specifically developed for Azure and hence they are not cloud agnostic. The main difference is that Bicep is a lot more human readable and generally you need fewer lines of code to achieve the same result through the use of simple syntax. &lt;/p&gt;

&lt;p&gt;You can read more about Bicep &lt;a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some pre-requisites before we get started: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure CLI - Download here &lt;a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/cli/azure/install-azure-cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Azure DevOps Access&lt;/li&gt;
&lt;li&gt;Azure Subscription&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Converting an ARM Template to Bicep
&lt;/h2&gt;

&lt;p&gt;With the Azure CLI Microsoft provides functionality for you to decompile an ARM/JSON template to Bicep. This is extremely useful if you have existing deployments, and you want to start managing them with Bicep. &lt;/p&gt;

&lt;p&gt;For this tutorial we are going to perform a simple resource group deployment. Let's look at our ARM template. It's split into a &lt;code&gt;template.json&lt;/code&gt; file which has the definition of what we want to deploy and a &lt;code&gt;parameters.json&lt;/code&gt; where we provide specific named variables required by the &lt;code&gt;template.json&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;It's worth noting that the parameters file does not need to be converted. This remains in a JSON format. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Template File&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contentVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rgName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rgLocation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"defaultValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"variables"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Microsoft.Resources/resourceGroups"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"apiVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2018-05-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[parameters('rgLocation')]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[parameters('rgName')]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[parameters('tags')]"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Parameter File&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"contentVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rgName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arm-template-test-rg"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rgLocation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"australiaeast"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Let's convert our template to Bicep&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1: Make sure you have the Azure CLI installed on your local machine. Navigate to the folder containing your ARM template and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az bicep decompile --file template.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00acshtz5zl0srhe4igs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00acshtz5zl0srhe4igs.png" alt=" " width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the process completes, you will notice another file has been added to the directory called &lt;code&gt;template.bicep&lt;/code&gt;. This is our converted template.&lt;/p&gt;

&lt;p&gt;2: Let's review the code for our new bicep file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;targetScope = 'subscription'
param rgName string
param rgLocation string
param tags object = {
}

resource rg 'Microsoft.Resources/resourceGroups@2018-05-01' = {
  location: rgLocation
  name: rgName
  properties: {
  }
  tags: tags
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you can notice straight away how the template is much shorter and more concise. We went from 28 lines of code to 13. It's worth noting that the conversion for bigger files can have some issue with naming formats so you may need to do some polishing before it's done. We can use our existing JSON parameter file to supply the values required. &lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy the template manually
&lt;/h2&gt;

&lt;p&gt;Let's deploy the template manually to make sure it works using the Azure CLI. &lt;/p&gt;

&lt;p&gt;1: Run the following Azure CLI command. Make sure to replace the template and parameter file name if you used something different in the previous step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deployment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestDeployment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;australiaeast&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--template-file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;template.bicep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--parameters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parameters.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg12s2ejujmsgm909cbsh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg12s2ejujmsgm909cbsh.png" alt=" " width="800" height="113"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can now see the RG is ready in the Azure portal. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxmkmkebx8a448zbr72m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxmkmkebx8a448zbr72m.png" alt=" " width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's delete the RG and deploy the same templates using Azure DevOps for full automation. &lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy the template using Azure DevOps
&lt;/h2&gt;

&lt;p&gt;1: Create a new pipeline from Azure DevOps by navigating to &lt;code&gt;Pipelines&lt;/code&gt;and then &lt;code&gt;New pipelines&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgoqq0omrl9le6xew9qr1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgoqq0omrl9le6xew9qr1.png" alt=" " width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2: Select &lt;code&gt;Azure Repos Git&lt;/code&gt; for your code repository&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7zrotwn3tqgsutd3d9x8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7zrotwn3tqgsutd3d9x8.png" alt=" " width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3: Select &lt;code&gt;Starter pipeline&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94imjmxrjj6bgcixw8yn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94imjmxrjj6bgcixw8yn.png" alt=" " width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4: Paste the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Bicep files&lt;/span&gt;


&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImageName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest'&lt;/span&gt;

  &lt;span class="na"&gt;azureServiceConnection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;devops-poc3-deploy-spi'&lt;/span&gt;
  &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;australiaeast'&lt;/span&gt;
  &lt;span class="na"&gt;templateFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bicep/azure_resource_group/template.bicep'&lt;/span&gt;
  &lt;span class="na"&gt;templateParameterFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bicep/azure_resource_group/parameters.json'&lt;/span&gt;
&lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(vmImageName)&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AzureResourceManagerTemplateDeployment@3&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;deploymentScope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Subscription'&lt;/span&gt;
    &lt;span class="na"&gt;azureResourceManagerConnection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(azureServiceConnection)'&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Update&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Resource&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Group'&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(location)'&lt;/span&gt;
    &lt;span class="na"&gt;templateLocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Linked&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;artifact'&lt;/span&gt;
    &lt;span class="na"&gt;csmFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.SourcesDirectory)/$(templateFile)'&lt;/span&gt;
    &lt;span class="na"&gt;csmParametersFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.SourcesDirectory)/$(templateParameterFile)'&lt;/span&gt;
    &lt;span class="na"&gt;deploymentMode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Incremental'&lt;/span&gt;
    &lt;span class="na"&gt;deploymentName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DeployPipelineTemplate'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this scenario I am providing the template and parameter file as a linked artifact in Azure DevOps by specifying the location in the following parameters &lt;code&gt;csmFile&lt;/code&gt; &amp;amp; &lt;code&gt;csmParametersFile&lt;/code&gt;. The pipeline also has a trigger for CD so every merge to the main/master branch will re-run it. Since we are using incremental deployments this is pretty safe to do.&lt;/p&gt;

&lt;p&gt;5: Save the pipeline and run it. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklchwfjzp9kirqtgzchv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklchwfjzp9kirqtgzchv.png" alt=" " width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have now deployed the resource group through Azure DevOps using Bicep.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdgqeo7eofu2s8iquokgr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdgqeo7eofu2s8iquokgr.png" alt=" " width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next post we are going to look at how to perform some automated tests on our Bicep deployment using the same pipelines including code linting, deployment validation, what-if checks and approval gates. &lt;/p&gt;

</description>
      <category>docker</category>
      <category>container</category>
      <category>kubernetes</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Microsoft Defender for DevOps (Preview)</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Mon, 07 Nov 2022 15:02:25 +0000</pubDate>
      <link>https://dev.to/lakkimartin/microsoft-defender-for-devops-preview-k25</link>
      <guid>https://dev.to/lakkimartin/microsoft-defender-for-devops-preview-k25</guid>
      <description>&lt;p&gt;Microsoft recently announced the new Defender for &lt;a href="https://learn.microsoft.com/en-us/azure/defender-for-cloud/defender-for-devops-introduction" rel="noopener noreferrer"&gt;DevOps&lt;/a&gt; which is an extension built in directly into Microsoft Defender for Cloud now allowing you to monitor and manage security across a number of pipeline environments including Github and Azure DevOps. &lt;/p&gt;

&lt;p&gt;Some of the features include: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vulnerability management in open source code. &lt;/li&gt;
&lt;li&gt;Exposed secrets in code that have been hard coded.&lt;/li&gt;
&lt;li&gt;Container image scanning&lt;/li&gt;
&lt;li&gt;Pull Request annotations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This has been long awaited and finally it's here. In this short post we will go through how to on-board the feature using Defender for Cloud in Azure. &lt;/p&gt;

&lt;p&gt;The Microsoft Security DevOps extension currently supports the following open source tools: &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/PyCQA/bandit" rel="noopener noreferrer"&gt;Bandit&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/PyCQA/bandit/blob/master/LICENSE" rel="noopener noreferrer"&gt;Apache License 2.0&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/Microsoft/binskim" rel="noopener noreferrer"&gt;BinSkim&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Binary--Windows, ELF&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/microsoft/binskim/blob/main/LICENSE" rel="noopener noreferrer"&gt;MIT License&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/eslint/eslint" rel="noopener noreferrer"&gt;ESlint&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/eslint/eslint/blob/main/LICENSE" rel="noopener noreferrer"&gt;MIT License&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="//detect-exposed-secrets.md"&gt;Credscan&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Credential Scanner (also known as CredScan) is a tool developed and maintained by Microsoft to identify credential leaks such as those in source code and configuration files &lt;br&gt; common types: default passwords, SQL connection strings, Certificates with private keys&lt;/td&gt;
&lt;td&gt;Not Open Source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/Azure/template-analyzer" rel="noopener noreferrer"&gt;Template Analyzer&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ARM template, Bicep file&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Azure/template-analyzer/blob/main/LICENSE.txt" rel="noopener noreferrer"&gt;MIT License&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/accurics/terrascan" rel="noopener noreferrer"&gt;Terrascan&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Terraform (HCL2), Kubernetes (JSON/YAML), Helm v3, Kustomize, Dockerfiles, Cloud Formation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/accurics/terrascan/blob/master/LICENSE" rel="noopener noreferrer"&gt;Apache License 2.0&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/aquasecurity/trivy" rel="noopener noreferrer"&gt;Trivy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;container images, file systems, git repositories&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/aquasecurity/trivy/blob/main/LICENSE" rel="noopener noreferrer"&gt;Apache License 2.0&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can customise the extension and only run a selection of these tools which we will look at later in this post. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the Azure DevOps connector in Microsoft Defender
&lt;/h2&gt;

&lt;p&gt;At the time of writing, Defender for DevOps is currently in preview and only available in the Central US Azure region. &lt;/p&gt;

&lt;p&gt;You will need the following permissions to get this configure: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organisation administrator in Azure DevOps &lt;/li&gt;
&lt;li&gt;Security Administrator role in Defender for Cloud.&lt;/li&gt;
&lt;li&gt;Contributor role on the subscription you are creating the connector in. &lt;/li&gt;
&lt;li&gt;In Azure DevOps, configure: Third-party applications gain access via OAuth, which must be set to &lt;code&gt;On&lt;/code&gt; . &lt;a href="https://dev.to/azure/devops/organizations/accounts/change-application-access-policies?view=azure-devops"&gt;Learn more about OAuth&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;1: Open up Microsoft Defender for Cloud in the Azure Portal and navigate to DevOps Security&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feapn1cfsqddwxldjh731.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feapn1cfsqddwxldjh731.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2: Select "Add Connector" and choose Azure DevOps &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvq7x0vrc5dk2xxb3kvux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvq7x0vrc5dk2xxb3kvux.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3: Choose a Resource Group and give your connector a name (globally unique). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqhk0y6d9y2gvu63ihzr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqhk0y6d9y2gvu63ihzr.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4: Select the plan shown. Currently this is free as the service is in preview&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz3xb6joif87s54jiaksd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz3xb6joif87s54jiaksd.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5: On the next section select "Authorize". This will authorise your account and give Microsoft Defender permissions to your Azure DevOps organisation. You will see a pop up appear which will prompt for sign in. Make sure you sign in with the right account and have the right organisation selected if you are part of multiple. This caught me out on the first attempt and the creation failed. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vl8ivpon6a3m7c9l3th.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vl8ivpon6a3m7c9l3th.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;6: Next on the same screen you will need to select which Azure DevOps projects and repositories you want to grant access to. You can either select specific ones or use auto discovery which will on-board everything and any future ones that are created. &lt;/p&gt;

&lt;p&gt;7: Review the configuration and select "create". &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0zbi4qwhxtububfsbyi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0zbi4qwhxtububfsbyi.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;8: You should then see the environment successfully connected in Microsoft Defender in the Azure DevOps security blade: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmh9dqyl8mwmqo5ybeitt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmh9dqyl8mwmqo5ybeitt.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next we will look at the installing the extensions in Azure DevOps and setting up the pipeline required to perform the scans. &lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the extensions and pipeline in Azure DevOps
&lt;/h2&gt;

&lt;p&gt;1: Navigate to Azure DevOps and select manage extensions at the top right of your screen: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foumyhx1fgnbyqcuvnv1u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foumyhx1fgnbyqcuvnv1u.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2: Select "Browse marketplace" and install the following extensions: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Microsoft Security DevOps&lt;/li&gt;
&lt;li&gt;SARIF SAST Scans Tab (Add a tab to your build to show the scan results) &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fstzfq4dpfa1l1uuawpfp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fstzfq4dpfa1l1uuawpfp.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure the security scan pipeline
&lt;/h2&gt;

&lt;p&gt;In order to scan our code we need to configure the pipeline with the required tooling from Microsoft that runs the extension. &lt;/p&gt;

&lt;p&gt;1: Create a new starter pipeline in your repository and paste the following code (more detailed instructions provide by MS &lt;a href="https://learn.microsoft.com/en-gb/azure/defender-for-cloud/azure-devops-extension#configure-your-pipelines-using-yaml" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Starter pipeline&lt;/span&gt;
&lt;span class="c1"&gt;# Start with a minimal pipeline that you can customize to build and deploy your code.&lt;/span&gt;
&lt;span class="c1"&gt;# Add steps that build, run tests, deploy, and more:&lt;/span&gt;
&lt;span class="c1"&gt;# https://aka.ms/yaml&lt;/span&gt;
&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;
&lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;windows-latest'&lt;/span&gt;
&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UseDotNet@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dotnet'&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.1.x&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UseDotNet@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dotnet'&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5.0.x&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UseDotNet@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dotnet'&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;6.0.x&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MicrosoftSecurityDevOps@1&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Microsoft&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Security&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;DevOps'&lt;/span&gt; 
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;categories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;IaC,secrets'&lt;/span&gt;
    &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;terrascan,credscan'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99gth07z0gvakhwztdyj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99gth07z0gvakhwztdyj.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I mainly store Terraform code in my selected repository and so I have specified that I want to run the &lt;code&gt;terrascan&lt;/code&gt; and 'credscan" tool and I want to specifically scan for Infrastructure As Code misconfiguration and exposed secrets in my code.&lt;/p&gt;

&lt;p&gt;2: Run the pipeline and wait for the job to complete. Once this is done you can view the results in the scan tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiqumh7kowxq67ap76587.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiqumh7kowxq67ap76587.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3: You can now view the results in the scan tab. You can see I have quite a few areas in TF where adjustments are needed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18f0q9y1w1674je8qxld.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18f0q9y1w1674je8qxld.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also customise the sensitivity of the scans. More information on how to configure the analysers on &lt;a href="https://github.com/microsoft/security-devops-action/wiki#table-of-contents" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally the scan results are also published as an artifact:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzy7f4y1yzyimq0z1dxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzy7f4y1yzyimq0z1dxu.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8vzqnmjsbt56vdpeidcl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8vzqnmjsbt56vdpeidcl.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4: Next you can navigate back to Microsoft Defender for Cloud in Azure and view the results and security posture directly there giving you a unified single pane of glass: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5lbir5kikht1xnqyo6ma.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5lbir5kikht1xnqyo6ma.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable pull request annotations
&lt;/h2&gt;

&lt;p&gt;Defender for DevOps allows you to expose the above security findings as annotations in pull requests so developers know where issue need to be resolved before the code is merged into the main branch. This prevents issues before any code reaches production. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enable build validation on your main branch in Azure DevOps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1: Sign in to Azure DevOps and navigate to project settings and then repositories&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuwhtiqcl57aim16k0tbv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuwhtiqcl57aim16k0tbv.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2: Select your repository and then navigate to policies and then select your main branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1qfjteiy6vlwc3o4taco.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1qfjteiy6vlwc3o4taco.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3: Navigate to the build validation section and enable the build validation with the default settings. Select the pipeline we created earlier in the build pipeline dropdown. Give it an appropriate name:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmocuen3u7hn8cfbt8el.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmocuen3u7hn8cfbt8el.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca9z1mph3qzsvsehwz46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca9z1mph3qzsvsehwz46.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enable pull request annotation in Defender for Cloud&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1: Login to the Azure portal and navigate to Defender for Cloud &amp;gt; DevOps Security&lt;/p&gt;

&lt;p&gt;2: Select the relevant repository to enable the pull request annotation and select configure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2tol0bjwz6lzm6jdff4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2tol0bjwz6lzm6jdff4.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3: Enable the annotations and select a category. Currently only secrets are supported whilst in preview. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61wsr42q7s4zxrfwipbw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61wsr42q7s4zxrfwipbw.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we are all set. I have intentionally hard coded a secret in my repository and created a pull request to show the annotations working. The pull request triggered the build validation we set up earlier which performs a new scan.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fze9p9zp9wi7c54ne67d2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fze9p9zp9wi7c54ne67d2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was just a crash course on this preview feature and there is defiantly a lot more left to explore however it's great to be able to perform these scans with a single extension and view them directly in Microsoft Defender now providing the capability not only to view your security posture in Azure but also in Azure DevOps. &lt;/p&gt;

</description>
      <category>devops</category>
      <category>azure</category>
      <category>security</category>
    </item>
    <item>
      <title>Create Azure AD groups in bulk using PowerShell with a CSV input.</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Mon, 26 Sep 2022 12:48:14 +0000</pubDate>
      <link>https://dev.to/lakkimartin/create-azure-ad-groups-in-bulk-using-powershell-with-a-csv-input-1n0e</link>
      <guid>https://dev.to/lakkimartin/create-azure-ad-groups-in-bulk-using-powershell-with-a-csv-input-1n0e</guid>
      <description>&lt;p&gt;I recently had to replicate an Azure AD group structure from one tenant to another and wrote a super simple script to speed things up. &lt;/p&gt;

&lt;h2&gt;
  
  
  Set up PowerShell
&lt;/h2&gt;

&lt;p&gt;Firstly you will need to install the Azure AD PowerShell module which allows you to intreact with AAD.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Install-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AzureADPreview&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authenticate to your Azure Active Directory tenant by running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Connect-AzureAD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-TenantId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;insert tenant id here&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Retrieve AD Groups
&lt;/h2&gt;

&lt;p&gt;I used a simple command to export the AD groups from the tenant I want to copy from to a csv file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-AzureADMSGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DisplayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Export-Csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c:\development\azureadusers.csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-NoTypeInformation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can modify the export path as needed.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Create the AD Groups
&lt;/h2&gt;

&lt;p&gt;Now that you have the AD groups exported you can run the script. Make sure you re-authenticate to the new Tenant where you want o set up the groups. Here is an example of what my CV looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OEHDfJPU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/14aor2fhip60zvjbohz0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OEHDfJPU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/14aor2fhip60zvjbohz0.png" alt="Image description" width="655" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that I am only filtering the Display Names of the AD groups without the descriptions and owners. I haven't had a chance to script this up but it's fairly straight forward to do. &lt;/p&gt;

&lt;p&gt;Run the script to set up the groups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$Groups&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Import-Csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\development\azureadusers.csv"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Groups&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;New-AzureAdGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DisplayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisplayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-MailEnabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$False&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-SecurityEnabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$True&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-MailNickName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NotSet"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AD groups are now created!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lXLTP8_y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/paookjm9t9dfrihqqc9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lXLTP8_y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/paookjm9t9dfrihqqc9d.png" alt="Image description" width="776" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are some limitations with the Azure AD module. It doesn't support all parameters so for example you can't enable groups to be assignable to roles. You can achieve the same by using the Az.Resources &lt;a href="https://learn.microsoft.com/en-us/powershell/module/az.resources/new-azadgroup?view=azps-8.3.0"&gt;module&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also expand the script to add descriptions to the AD groups and replicate/assign owners.&lt;/p&gt;

&lt;p&gt;Hope this helps.&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>azure</category>
      <category>devops</category>
    </item>
    <item>
      <title>Containerising a simple JavaScript application using Docker - Part 1</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Wed, 31 Aug 2022 21:51:40 +0000</pubDate>
      <link>https://dev.to/lakkimartin/containerising-a-simple-javascript-application-using-docker-part-1-3j2g</link>
      <guid>https://dev.to/lakkimartin/containerising-a-simple-javascript-application-using-docker-part-1-3j2g</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this tutorial we are going to be taking a simple Javascript application (source code taken from Docker) and running it in a container using Docker.&lt;/p&gt;

&lt;p&gt;This is the first of a series of posts on containers. Here is a breakdown of what's to come.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Part 1 will look at putting a simple app into a Docker container&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;2: Part 2 will look at building some persistent storage for our application using Dockeer.&lt;/p&gt;

&lt;p&gt;3: Part 3 will look at taking our application we containerised using Docker and putting it into a local Kubernetes deployment using minikube. &lt;/p&gt;

&lt;p&gt;4: Part 4 will look at taking our application and deploying it to an Azure Kubernetes Cluster and exposing it publicly.&lt;/p&gt;

&lt;p&gt;5: Part 5 will look at adding an ingress controller and WAF in front of our application using Azure Application Gateway.&lt;/p&gt;

&lt;p&gt;Docker has grown in adoption since it was first introduced in 2013. Docker engine is by no means the only containerisation technology out in the market. You can use open source tools such as Buildah or Podman to achieve the same results. Docker actually consists of a number of products however it is most commonly known for Docker engine.&lt;/p&gt;

&lt;p&gt;The images we are going to be building are of file type .Dockerfile however these are actually OCI images. &lt;a href="https://opencontainers.org/" rel="noopener noreferrer"&gt;OCI&lt;/a&gt; or open container initiative is an open source governance structure for creating open industry standards for container formats and runtimes. What this means is that you can any OCI formatted image is generally transportable to any container runtime supporting OCI and not just Docker so you could actually run your image using the &lt;a href="https://containerd.io/" rel="noopener noreferrer"&gt;containerd&lt;/a&gt; runtime or a runtime that supports OCI.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Containers vs Virtualisation
&lt;/h2&gt;

&lt;p&gt;Before we dive into the tutorial here is a comparison of containers vs virtualisation. In simple terms, containers allow you to run multiple applications on a single operating system on a single server whereas virtualisation allows you to run multiple operating systems on a single physical server. This means containers are portable and lightweight as they do not carry the burden of the operating system. You can run containers either on bare metal servers or more commonly on virtual machines so typically containers run on top of OS virtualisation. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw83zjct2lu2419ttcatc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw83zjct2lu2419ttcatc.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;There are some tools required in order to put our sample application into Docker as listed below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Docker &lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Desktop&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/windows/wsl/install" rel="noopener noreferrer"&gt;WSL2&lt;/a&gt; if you are using windows. This allows you to run a Linux subsystem if your are using a windows machine. If you are using Mac OS x then this is not required.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;Git&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting our app in a container
&lt;/h2&gt;

&lt;p&gt;The application we are going to containerise is a simple to do list built using Javascript. I took the sample code from the Docker docs any take no credit for building it.  &lt;/p&gt;

&lt;p&gt;1: Start of by creating a fork of my repository located on &lt;a href="https://github.com/lakovmartin/kubernetes-tutorial" rel="noopener noreferrer"&gt;Github&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;2: Clone the repository to your local machine by running &lt;code&gt;Git clone&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;3: Navigate to the &lt;code&gt;docker/app&lt;/code&gt; directory where you will see the Dockerfile for the application and the source code which we will compile when building the image.&lt;/p&gt;

&lt;p&gt;Before we build the image let's breakdown the instructions in the Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. These are what's used to build our image.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:12-alpine&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; python2 g++ make
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--production&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "src/index.js"]&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;From&lt;/strong&gt; - Here we are specifying an existing image from the public Docker repository to use as a base. &lt;br&gt;
&lt;strong&gt;Run&lt;/strong&gt; - RUN instruction is used to executes any commands on top of the current image and this will create a new layer.&lt;br&gt;
&lt;strong&gt;WORKDIR&lt;/strong&gt; - &lt;br&gt;
&lt;strong&gt;COPY&lt;/strong&gt; - COPY instruction is used to copy files, directories and remote URL files to the destination within the filesystem of the Docker Images.&lt;br&gt;
&lt;strong&gt;CMD&lt;/strong&gt; - CMD instruction is used to set a command to be executed when running a container. There must be only one CMD in a Dockerfile.&lt;br&gt;
&lt;strong&gt;EXPOSE&lt;/strong&gt; - EXPOSE instruction is used to inform about the network ports that the container listens on runtime&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's build the image&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;4: Run &lt;code&gt;Docker Build -t todo-list&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4l12ysoffo0711axyvtd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4l12ysoffo0711axyvtd.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image will take a couple minutes to build. Once it's complete we can check the image exists by listing our docker images:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

Docker image ls


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewk44gdddadeqpz970eq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewk44gdddadeqpz970eq.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see our image was built successfully and is tagged "latest". We can optionally use semantic versioning. &lt;/p&gt;

&lt;p&gt;5: Let's run the application locally: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

docker run -dp 3000:3000 todo-list


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should now be able to access the to-do list app on &lt;code&gt;http://localhost:3000/&lt;/code&gt; in your browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1kvokhpn0t2174ckbj9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1kvokhpn0t2174ckbj9.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-dp&lt;/code&gt; flag is telling Docker to detach the container and map the application port of 3000 to a local port on our machine and in this it's 3000. You can map the port the application is listening to on any port available on your machine. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next steps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Currently any items added to the to-list will be removed once the container is stopped. In the next post we will attach some persistent storage to our application that will allow us to persist the items added to the list in local storage when the container is stopped so we don't loose the data. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>docker</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Azure Load Testing (PaaS) using Apache JMeter</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Mon, 27 Jun 2022 22:04:13 +0000</pubDate>
      <link>https://dev.to/lakkimartin/azure-load-testing-paas-using-jmeter-2m5j</link>
      <guid>https://dev.to/lakkimartin/azure-load-testing-paas-using-jmeter-2m5j</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Azure Load Testing is a new managed load testing service from Microsoft (currently in preview) that lets you simulate volumes of traffic to your website regardless of where you are hosting your application. It uses Apache JMeter to both define and run the loads tests.&lt;/p&gt;

&lt;p&gt;This can be specifically useful if you have a website that needs to scale on demand to meet peak performance requirements. Microsoft seem to be adding more abstraction layers in Azure which is nice to see and previously you would have to run these JMeter tests on your own compute environment or virtual machines. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3Ca9HoxG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ks9vop2n5ndrlyxy2cpt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3Ca9HoxG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ks9vop2n5ndrlyxy2cpt.png" alt="Image description" width="764" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article we are going to look at how you can create a sample load testing in the portal. The service is currently in preview and it seems like only manual deployment through the portal is available. There is also a comprehensive guide from MS on how you can can automate the load testing through CI/CD using Github actions so I won't be repeating something that is already well &lt;a href="https://docs.microsoft.com/en-us/azure/load-testing/tutorial-cicd-github-actions"&gt;documented&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the Load Testing Service
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Navigate to the Azure marketplace and search for "Azure Load Testing". You can also try the following &lt;a href="https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/Microsoft.LoadTestService%2FLoadTests"&gt;link&lt;/a&gt; after you have logged into the portal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Fill out the required details. In my case I set up a new resource group for the service and click "create". &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Fpix1dL3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ugsj03brzer2lg9br3cg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Fpix1dL3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ugsj03brzer2lg9br3cg.png" alt="Image description" width="880" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Navigate to the service once it's deployed. It should spin up in a few minutes. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KKyNvq6q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9c1vlapikyr430zniiy6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KKyNvq6q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9c1vlapikyr430zniiy6.png" alt="Image description" width="880" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the Load Test and View the Results
&lt;/h2&gt;

&lt;p&gt;For the purposes of this demo we are going to set up one of the out the box tests which will hit an endpoint we specify. You can optionally upload your own JMeter scripts. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Navigate to the "tests" blade in the left pane&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Qk5F52d---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0jpy3nuaeufsblvocx4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Qk5F52d---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0jpy3nuaeufsblvocx4.png" alt="Image description" width="880" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Input the required parameters. Here you can specify how many users you would like to simulate the load for, the test duration and the ramp up time for the virtual users. For the purposes of this demo we are going to load &lt;code&gt;www.google.co.uk&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l4_rkwjN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/isopvljjxpeglpf7yf0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l4_rkwjN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/isopvljjxpeglpf7yf0w.png" alt="Image description" width="880" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; The test will start running and it should complete based on the duration specified in the previous step:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9IueN3Ai--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6lxao3t3t9tg5u5kcc22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9IueN3Ai--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6lxao3t3t9tg5u5kcc22.png" alt="Image description" width="880" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Once the test completes you can navigate over and analyse the results: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kD4ap2As--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ijs5yxrjwd3hgslgjbmd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kD4ap2As--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ijs5yxrjwd3hgslgjbmd.png" alt="Image description" width="880" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can optionally export the results. For this test we defined no pass criteria so the test result will display as "not applicable". &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This is certainly a neat new service to see from Microsoft. The big issue with load testing is having to spin up compute to execute your tests and then integrating this into your CI/CD from scratch. A lot of this friction is removed with this service. &lt;/p&gt;

&lt;p&gt;This is only a fraction of the functionality so be sure to check out the Microsoft &lt;a href="https://docs.microsoft.com/en-us/azure/load-testing/overview-what-is-azure-load-testing"&gt;docs&lt;/a&gt; for more info.&lt;/p&gt;

&lt;p&gt;Microsoft also provide full CI/CD &lt;a href="https://docs.microsoft.com/en-us/azure/load-testing/tutorial-cicd-github-actions"&gt;integration&lt;/a&gt; for Azure load testing using Github actions meaning you can automatically run these tests when creating a PR (or any other trigger) and define pass/fail criteria which embeds load testing into the core of the software development lifecycle. I can also see a use for this when deploying infrastructure changes through IaC where changing the sizing of your compute may impact the performance when under high load. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>github</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Deploy Azure DevOps Self-Hosted Build Agents on Kubernetes (AKS) and scale them using KEDA</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Wed, 22 Jun 2022 14:07:17 +0000</pubDate>
      <link>https://dev.to/lakkimartin/deploy-azure-devops-self-hosted-build-agents-on-kubernetes-aks-and-scale-them-using-keda-dkc</link>
      <guid>https://dev.to/lakkimartin/deploy-azure-devops-self-hosted-build-agents-on-kubernetes-aks-and-scale-them-using-keda-dkc</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;KEDA is an event driven autoscaler for Kubernetes that allows you to scale containers based on events. &lt;/p&gt;

&lt;p&gt;It is a lightweight single purpose component that can be added to any Kubernetes cluster. Keda can also work alongside the Kubernetes horizontal autoscaler. &lt;/p&gt;

&lt;p&gt;The diagram below (taken from the Keda docs) shows how Keda integrates with the horizontal pod autoscalers, external events and the Kubernetes API:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13ctfb8w8a65uyzaxkzo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13ctfb8w8a65uyzaxkzo.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More information on Keda can be found in the official &lt;a href="https://keda.sh/docs/2.7/concepts/#architecture" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this tutorial will be going over how you can use deploy self-hosted build agents on to an Azure Kubernetes Cluster and scale them using KEDA (&lt;a href="https://keda.sh/docs/2.3/concepts/scaling-jobs/" rel="noopener noreferrer"&gt;scaled jobs&lt;/a&gt;) based on the number of jobs in a build queue. &lt;/p&gt;

&lt;p&gt;As a side note, Azure Container Apps also supports KEDA and scaling using the pipelines trigger however KEDA kills containers half through a job and scaling jobs are not yet supported in Container Apps.&lt;/p&gt;

&lt;p&gt;All of the code for this project can be found on my Github page &lt;a href="https://github.com/lakovmartin/azure/tree/main/kubernetes/keda-devops-agents" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools Required
&lt;/h2&gt;

&lt;p&gt;We will be using the following tools so make sure you have them installed on your local machine. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Helm (Kubernetes Package Manager) - Install &lt;a href="https://helm.sh/docs/intro/install/" rel="noopener noreferrer"&gt;Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Azure CLI - Install &lt;a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" rel="noopener noreferrer"&gt;Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kubernetes Command Line - Install &lt;a href="https://docs.microsoft.com/en-us/cli/azure/aks#az-aks-install-cli" rel="noopener noreferrer"&gt;Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Terraform (Infrastructure as Code) - Install &lt;a href="https://learn.hashicorp.com/tutorials/terraform/install-cli" rel="noopener noreferrer"&gt;Guide&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Docker Desktop - Install &lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Guide&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deploy Azure resourcse
&lt;/h2&gt;

&lt;p&gt;Before we get started we need to deploy the Azure components that will host our solution. These are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure Resource Group&lt;/li&gt;
&lt;li&gt;AKS Cluster&lt;/li&gt;
&lt;li&gt;Container Registry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Kubernetes cluster will be basic with a single pool and single virtual machine. No advanced networking will be used for the purposes of proving the concept.&lt;/p&gt;

&lt;p&gt;We will be deploying this through Terraform which is an infrastructure as code deployment tool. The Terraform deployment file can be found in my Git repository here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Fork the repository and create a local clone on your machine &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Navigate to the &lt;a href="https://github.com/lakovmartin/azure/tree/main/kubernetes/keda-devops-agents/iac" rel="noopener noreferrer"&gt;folder&lt;/a&gt; that contains the &lt;code&gt;main.tf&lt;/code&gt; file and run: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;

&lt;span class="nx"&gt;Terraform&lt;/span&gt; &lt;span class="nx"&gt;Init&lt;/span&gt; 


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6hu7rul4y0nnstr59df.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6hu7rul4y0nnstr59df.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will initialise Terraform and create the local state file.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Now we are going to run Terraform plan which will list the resources that will be deployed. It should be 4 in total as listed above (1 resource is a role assignment to link AKS to ACR).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;

&lt;span class="nx"&gt;Terraform&lt;/span&gt; &lt;span class="nx"&gt;Plan&lt;/span&gt; 


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lp98rdxveo43ymy4qcw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lp98rdxveo43ymy4qcw.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Once we are happy with what Terraform is going to deploy we can run the apply stage which will deploy the resources into Azure. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;

&lt;span class="nx"&gt;Terraform&lt;/span&gt; &lt;span class="nx"&gt;Apply&lt;/span&gt; 


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4z188ixhxnc0hqiaihlh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4z188ixhxnc0hqiaihlh.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should now see the resources in the Azure portal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2g3gfu5kbyv3t2yl9mo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2g3gfu5kbyv3t2yl9mo.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the docker images
&lt;/h2&gt;

&lt;p&gt;Next we need to build the Docker image for the Azure DevOps self-hosted agents. Microsoft have documented this quite for Docker &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/docker?view=azure-devops" rel="noopener noreferrer"&gt;here&lt;/a&gt;. I have modified the image slightly to include PowerShell and we are using run.sh rather than docker in the start.sh script as ContainerD is the new container runtime in AKS version 1.19 and higher. You can re-use my images for these next steps from my Github &lt;a href="https://github.com/lakovmartin/azure/tree/main/kubernetes/keda-devops-agents" rel="noopener noreferrer"&gt;repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Start up docker Desktop&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Navigate to the repository you cloned earlier and navigate to the &lt;a href="https://github.com/lakovmartin/azure/tree/main/kubernetes/keda-devops-agents/images" rel="noopener noreferrer"&gt;folder&lt;/a&gt; that contains the docker image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Let's build the image and tag it: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

Docker build -f &amp;lt;docker-image-path&amp;gt; -t &amp;lt;tag&amp;gt; 


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can test the image by running the container locally which will register it in DevOps. Just supply the environment variables.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

docker run -e AZP_URL= -e AZP_TOKEN= -e AZP_AGENT_NAME= -e AZP_POOL= &amp;lt;image&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Let's login to the Azure Container Registry.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

Docker login &amp;lt;login-server&amp;gt; -u &amp;lt;username&amp;gt; -p &amp;lt;password&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can get the above parameters from the container registry in the portal: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtvgtvbygptc4tea0rlv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtvgtvbygptc4tea0rlv.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; Let's push the image to the Azure Container Registry&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

Docker push &amp;lt;imagename:tag&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The image should now be in the container registry which allow the containers running the agents to pull it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Install KEDA on to the AKS cluster
&lt;/h2&gt;

&lt;p&gt;KEDA runs in a container on the Kubernetes cluster and it's not built in so we need to install it. The KEDA pod handled all of the event driven scaling. We are going to use Helm to the install however you can apply the manifests &lt;a href="https://keda.sh/docs/2.7/deploy/" rel="noopener noreferrer"&gt;directly&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Make sure you are authenticated to thee AKS cluster before running these next steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Add the helm repo &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

helm repo add kedacore https://kedacore.github.io/charts


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Update the helm repo&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

helm repo update


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Create a new namespace and install the KEDA helm chart&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

kubectl create namespace keda
helm install keda kedacore/keda --namespace keda


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcb1ljatgvx23yj5284v6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcb1ljatgvx23yj5284v6.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should now see the KEDA pods running in the keda namespace: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9209wg7co14ywymamp5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9209wg7co14ywymamp5w.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are now ready to start using KEDA for scaling our containers.   &lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying the agents
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Set up a new Agent Pool in Azure DevOps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before we apply the Kubernetes manifests we need to set up a new agent pool in DevOps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgbpj2lf4mini9lymbscn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgbpj2lf4mini9lymbscn.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Set up a self hosted pool on the organisation level. Take a note of the pool id. This can be found in the URL when you select the pool:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.azure.com/organisation/_settings/agentpools?poolId=16&amp;amp;view=jobs" rel="noopener noreferrer"&gt;https://dev.azure.com/organisation/_settings/agentpools?poolId=16&amp;amp;view=jobs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case it's 16.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; You will also need to generate a PAT &lt;a href="https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;amp;tabs=Windows" rel="noopener noreferrer"&gt;token&lt;/a&gt; with Agent Pools read &amp;amp; manage permissions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; You will need to encode the token to Base64. You can do this either through Bash or through this &lt;a href="https://www.base64encode.org/" rel="noopener noreferrer"&gt;website&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apply the manifests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because we are going to be using scaling jobs, we cannot specify idle agents. This creates a problem as you can't queue an Azure pipelines job on an empty agent pool so a workaround is to deploy a static agent and turn it offline. &lt;/p&gt;

&lt;p&gt;Apply the &lt;code&gt;deployment.yaml&lt;/code&gt; from the cloned &lt;a href="https://github.com/lakovmartin/azure/tree/main/kubernetes/keda-devops-agents/images" rel="noopener noreferrer"&gt;repository&lt;/a&gt; by running: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

kubectl apply -f ./deployment.yaml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Make sure you replace the variables in the YAML for the agent pool name, organisation URL, image and PAT token. See examples below: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;image: = &lt;code&gt;&amp;lt;acr-name&amp;gt;.azurecr.io/&amp;lt;repository-name&amp;gt;:&amp;lt;tag&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;AZP_URL = &lt;code&gt;https://dev.azure.com/&amp;lt;organisation-name&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;AZP_TOKEN = Base64 encoded token that we created earlier.&lt;/li&gt;
&lt;li&gt;AZP_POOL = Name of the self-hosted agent pool in Azure DevOps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have applied the manifest you should see the pod running in the default namespace:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkslp7n16y2gs5pfanlt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkslp7n16y2gs5pfanlt.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can now see the agent running in DevOps:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1p4jggl4dxyqou24s1q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1p4jggl4dxyqou24s1q.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disable the agent and leave it running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apply the KEDA Scale Job Manifest
&lt;/h2&gt;

&lt;p&gt;The final step is to apply the scaled job object through our &lt;code&gt;keda-scaled-jobs.yaml&lt;/code&gt; manifest. You will need to replace the values for the image, organisation, pool, token, and pool ID.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; ./keda-scaled-jobs.yaml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1nuwb5jadxuyi9et1c4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl1nuwb5jadxuyi9et1c4.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have applied the scale object, KEDA will be listening to the build queue every 10 seconds (can be customised). I have included a load testing &lt;a href="https://github.com/lakovmartin/azure/blob/main/kubernetes/keda-devops-agents/test/load-test.yaml" rel="noopener noreferrer"&gt;pipeline&lt;/a&gt; for Azure DevOps that triggers 10 jobs which run some PowerShell loops. Let's run some jobs and see the scaling in action: &lt;/p&gt;

&lt;p&gt;Pods are spinning up:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4e2p6w6fg98htltsv19t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4e2p6w6fg98htltsv19t.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Agents are coming online:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0f8ykgg3ha68muc92i9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0f8ykgg3ha68muc92i9.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have now configured our self-hosted agents to run in Docker using Kubernetes as the orchestrator and KEDA as the scaling engine!  &lt;/p&gt;

&lt;p&gt;You can customise KEDA on how often it monitors the job queue, maximum number of replicas and more. &lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;I have discovered some limitations with Keda and scaling jobs which I'm working on resolving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you run some jobs in DevOps and cancel them the containers keep running.&lt;/li&gt;
&lt;li&gt;KEDA does not remove any pods in a "completed" state. This can be resolved with a custom clean up shell script running as a cron on the cluster however it may be possible to do it with KEDA.&lt;/li&gt;
&lt;li&gt;Offline agents are not removed from the DevOps pool. I have a cron job running in Azure DevOps that cleans this up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;All of the above steps can of course be automated through a simple build pipeline either through Github actions or Azure DevOps pipelines. &lt;/p&gt;

&lt;p&gt;I will be building all of this functionality into a Helm chart that will also support Github runners so keep an eye out!&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>azure</category>
      <category>devops</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Import your existing Azure infrastructure into Terraform using Azure Terrafy</title>
      <dc:creator>Martin</dc:creator>
      <pubDate>Mon, 13 Jun 2022 21:31:50 +0000</pubDate>
      <link>https://dev.to/lakkimartin/import-your-existing-azure-infrastructure-into-terraform-using-azure-terrafy-p20</link>
      <guid>https://dev.to/lakkimartin/import-your-existing-azure-infrastructure-into-terraform-using-azure-terrafy-p20</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Terraform is a great tool for defining your infrastructure using code and getting it deployed to Azure or your chosen cloud provider. One of the many challenges you may face is importing existing infrastructure into Terraform so that you can then manage it through code along with the benefits that come along with that.&lt;/p&gt;

&lt;p&gt;You will often hear the term &lt;strong&gt;state file&lt;/strong&gt;. Terraform must store state about your managed infrastructure and configuration. Thee state is used by Terraform to map real world resources in the cloud back to your configuration and to keep track of metadata.&lt;/p&gt;

&lt;p&gt;This state is stored by default in a local file named &lt;code&gt;terraform.tfstate&lt;/code&gt; however it can also be stored remotely and more commontly in an Azure storage account as a blog which is often what's chosen by large scale enterprises to avoid having local state files.&lt;/p&gt;

&lt;p&gt;There are usually 3 steps that typically need to be completed in order to import your cloud resource into Terraform using the process provided by Hashicorp:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define your infrastructure using Terraform. &lt;/li&gt;
&lt;li&gt;Run Terraform import to update your state file.&lt;/li&gt;
&lt;li&gt;Run Terraform plan to verify the import is successful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This method is well defined in the Terraform &lt;a href="https://www.terraform.io/cli/import" rel="noopener noreferrer"&gt;docs&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In this tutorial we will be talking about a new tool called Azure &lt;a href="https://github.com/azure/aztfy" rel="noopener noreferrer"&gt;Terrafy&lt;/a&gt; developed by Microsoft which aims to automate the import process by generating both the configuration and state file for your existing infrastructure making it much easier to get started with Terraform. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can install Azure Terrafy using the Go Tool chain by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Azure&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;aztfy&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you haven't got Go then you can install it by following the instructions on the official Go &lt;a href="https://go.dev/doc/install" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also manually download the latest Binary from the Azure Terrafy Github &lt;a href="https://github.com/Azure/aztfy/releases/?WT.mc_id=modinfra-566685-apedward" rel="noopener noreferrer"&gt;page&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Just make sure to update your environment path so you can run Terrafy from your chosen shell.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows" rel="noopener noreferrer"&gt;This Stack Overflow article&lt;/a&gt; contains instructions for setting the PATH on Windows through the user interface.&lt;/p&gt;

&lt;p&gt;For Mac OS you can run &lt;code&gt;echo $PATH&lt;/code&gt; and place the executable/binary into one of the listed paths. &lt;/p&gt;

&lt;p&gt;Verify you have it correctly installed by running &lt;code&gt;aztfy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2a0almkit1mtccs0ax2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2a0almkit1mtccs0ax2o.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are now ready to start importing our infrastructure into Terraform .&lt;/p&gt;

&lt;h2&gt;
  
  
  Importing your Infrastructure
&lt;/h2&gt;

&lt;p&gt;Before we start the import we need to get the environment ready. Let's deploy an Azure Resource Group and a static web app resource using the Azure CLI and try to import it into Terraform using Terrafy to prove the concept. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create Azure Resource Group&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uksouth"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terafform-import-rg"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--subscription&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7309f068-5a47-4a28-851c-09979529cd8e"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get an output in the CLI once the RG provisioning is successful: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmaw2yzo4el882j74qde2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmaw2yzo4el882j74qde2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create an Azure Static Web App Resource&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's create an Azure Static Web App in the RG we created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;staticwebapp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;-n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;terraform-import-webapp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terafform-import-rg"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;\&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get an output in the CLI that the provisioning was successful. Validate the Static Web App is deployed by navigating to the Resource Group in the Azure Portal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wgano865x152bvfwsut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wgano865x152bvfwsut.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set up a new directory&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set up a new directory/folder where you want to Terrafy to store the Terraform files. By default it uses the root directory however you can also override this with a custom path.&lt;/p&gt;

&lt;p&gt;1: Run &lt;code&gt;mkdir &amp;lt;folder-name&amp;gt;&lt;/code&gt; to create a new folder.&lt;br&gt;
2: Navigate into the directory above &lt;code&gt;cd &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs2vg7nkf6qp1rut2lw06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs2vg7nkf6qp1rut2lw06.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Import the Infrastructure using Terrafy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make sure you have logged into Azure using by running &lt;code&gt;az login&lt;/code&gt; and set the subscription that contains the RG and web app created earlier by running &lt;code&gt;az account set -s &amp;lt;subscription_id&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;1: Run the import command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aztfy terafform-import-rg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terrafy will begin the import process by scanning the RG and presenting you with a list of resources to import. Navigation instructions are given at the bottom of your shell window. &lt;/p&gt;

&lt;p&gt;2: Select the resource you want to import. If you want to import all of them just hit W on your keyboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3111kmijr7wwjld4yo0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3111kmijr7wwjld4yo0.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case we want to import all our resources (static web app and RG) so we are going to hit W on our keyboard. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0sc9hkh0qg2gcxxj46zq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0sc9hkh0qg2gcxxj46zq.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Terrafy will begin the import process and the Terraform files will be stored in the root directory we created earlier. You will be prompted once import is complete:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Focmsuil55o2hlqi4wk6k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Focmsuil55o2hlqi4wk6k.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3: Verify the files have been generated:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffcksgpzieam4u7n2lm36.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffcksgpzieam4u7n2lm36.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see we now have our state and Terraform files. Viewing the main.tf file shows the definition for our static web app and resource group:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxs60jsrieug0ffqrxq14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxs60jsrieug0ffqrxq14.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;&lt;br&gt;
You should note that currently Azure Terrafy does not support all resource types so if you see "skip" listed next to the resource this means it cannot be imported.&lt;/p&gt;


&lt;h2&gt;
  
  
  Validate the Terraform state
&lt;/h2&gt;

&lt;p&gt;The final step is to validate that the Terraform state has been imported successfully by running Terraform. &lt;/p&gt;

&lt;p&gt;1: Run terraform init&lt;br&gt;
2: Run terraform plan&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnke6xhjmlzarm7u2lun8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnke6xhjmlzarm7u2lun8.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that Terraform is reporting that no changes are required and that our infrastructure in Azure matches our configuration or desired state in Terraform.&lt;/p&gt;

&lt;p&gt;You can also specify a remote backend before running aztfy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aztfy &lt;span class="nt"&gt;--backend-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;azurerm &lt;span class="nt"&gt;--backend-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;resource_group_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;resource group name&amp;gt; &lt;span class="nt"&gt;--backend-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;storage_account_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;account name&amp;gt; &lt;span class="nt"&gt;--backend-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;container_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;container name&amp;gt; &lt;span class="nt"&gt;--backend-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;terraform.tfstate &amp;lt;importing resource group name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have successfully imported our infrastructure into Terraform using Azure Terrafy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Currently the configuration is imported into a single main.tf files. Modules should be considered.&lt;/li&gt;
&lt;li&gt;Dependencies. Aztfy uses many of the dependencies in your resource group to map those out in the templates (see the 'depends_on' line declared in the main.tf above), much of that can be cleaned up once naming conventions and modules are implemented.&lt;/li&gt;
&lt;li&gt;Currently you can only import at the resource group level however support will be added for subscriptions and management groups.&lt;/li&gt;
&lt;li&gt;Not all resources are currently supported for the import process. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is certainly a great start to fully automating the TF import process and the tool will develop and grow making it much easier for enterprises to get started in managing their existing infrastructure with Terraform. &lt;/p&gt;

&lt;p&gt;The above import process can be fully automated by running all the tasks in a DevOps pipeline and specifying only the RG name. Terrafy supports non-interactive "batch" mode which is CI/CD friendly. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>terraform</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
