<?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: sandr0-p</title>
    <description>The latest articles on DEV Community by sandr0-p (@sandr0p).</description>
    <link>https://dev.to/sandr0p</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%2F983044%2F404f287c-2316-4743-90c2-6c14d861abba.png</url>
      <title>DEV Community: sandr0-p</title>
      <link>https://dev.to/sandr0p</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sandr0p"/>
    <language>en</language>
    <item>
      <title>A KeyVault for the Power Platform</title>
      <dc:creator>sandr0-p</dc:creator>
      <pubDate>Mon, 25 Nov 2024 19:00:00 +0000</pubDate>
      <link>https://dev.to/sandr0p/a-keyvault-for-the-power-platform-2jpo</link>
      <guid>https://dev.to/sandr0p/a-keyvault-for-the-power-platform-2jpo</guid>
      <description>&lt;p&gt;My friend and Microsoft MVP &lt;a href="https://dev.to/wyattdave"&gt;David Wyatt&lt;/a&gt; recently asked me to provide him with an Azure Key Vault for his Power Apps. "Easy", I thought, but everyone who ever worked with Microsoft products knows that "easy" is a relative term.&lt;/p&gt;




&lt;p&gt;We started by creating a standalone Key Vault. I added David and the Power Platform SPN with &lt;code&gt;Secret Get/List&lt;/code&gt; permissions, and we gave it a go. It will not surprise you that it didn't work (otherwise I wouldn't write, right?!). The Key Vault Firewall rejected the traffic.&lt;br&gt;
Fair, but thanks to our Company policies, we couldn't just allow public traffic. Which admittedly makes sense if you want to keep your secrets safe 🤷‍♂️&lt;/p&gt;



&lt;p&gt;In the next step, we added a couple of Power Platform IP addresses to the Key Vault Firewall. Unfortunately, it had the same outcome. The firewall rejected the traffic. It was at this moment we knew this was getting out of hand. It became actual work.&lt;br&gt;
As it turns out, we needed an entire Virtual Network with a Subnet and Network Security Group. If you ask me, this is too much work, but David insisted... and this is what it looks like:&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%2Ff3p1h6ca23gux98yn9h2.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%2Ff3p1h6ca23gux98yn9h2.png" alt="Network Architecture" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;As just mentioned, we need four resources. A Key Vault (&lt;code&gt;kv&lt;/code&gt;), a Virtual Network (&lt;code&gt;vnet&lt;/code&gt;), a Subnet (&lt;code&gt;subnet&lt;/code&gt;) and a Network Security Group (&lt;code&gt;nsg&lt;/code&gt;). To make matters worse, they have dependencies all over the place. If you are, like me, not a network specialist, this is a bit confusing. But here is what it looks like.&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%2Fmagg088bw8u0yixh5zt4.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%2Fmagg088bw8u0yixh5zt4.png" alt="Resource depencies" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;One of the resources without any dependencies is the &lt;code&gt;nsg&lt;/code&gt;, which makes it a prime starting point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource nsg 'Microsoft.Network/networkSecurityGroups@2024-03-01' = {
  location: 'eastus2'
  name: 'nsg'
  properties: {
    securityRules: [
      {
        name: 'AllowPP'
        properties: {
          access: 'Allow'
          destinationAddressPrefix: 'VirtualNetwork'
          destinationPortRange: '*'
          direction: 'Inbound'
          priority: 100
          protocol: '*'
          sourceAddressPrefix: 'PowerPlatformInfra'
          sourcePortRange: '*'
        }
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main focus here is on the &lt;code&gt;sourceAddressPrefix&lt;/code&gt; and &lt;code&gt;sourceAddressPrefix&lt;/code&gt;. These indicate from where to where we want to allow traffic. Usually, we would enter a list of IP addresses here, but Microsoft was so kind to provide us with &lt;a href="https://learn.microsoft.com/en-us/azure/virtual-network/service-tags-overview" rel="noopener noreferrer"&gt;Virtual Network Service Tags&lt;/a&gt;, which we can use instead of IP addresses.&lt;/p&gt;




&lt;p&gt;Next up, the &lt;code&gt;vnet&lt;/code&gt;. This is the second resource without a dependency.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource vnet 'Microsoft.Network/virtualNetworks@2024-03-01' = {
  name: 'vnet'
  location: 'eastus2'
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.0.0.0/16'
      ]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can choose your &lt;code&gt;addressPrefixes&lt;/code&gt; pretty much at random if this is an isolated network. If you intend to connect this network to another, make sure that you use a compatible IP range. The IP range in the example is a &lt;a href="https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing" rel="noopener noreferrer"&gt;CIDR notation&lt;/a&gt; and means that we can use the 65,536 IP addresses between &lt;code&gt;10.0.0.0&lt;/code&gt; and &lt;code&gt;10.0.255.255&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;Let's continue with the &lt;code&gt;subnet&lt;/code&gt;, which depends on the &lt;code&gt;vnet&lt;/code&gt; and &lt;code&gt;nsg&lt;/code&gt; which we have just defined.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource vnet 'Microsoft.Network/virtualNetworks@2024-03-01' existing = {
  name: 'vnet'
}

resource nsg 'Microsoft.Network/networkSecurityGroups@2024-03-01' existing = {
  name: 'nsg'
}

resource subnet 'Microsoft.Network/virtualNetworks/subnets@2024-03-01' = {
  name: 'default'
  parent: vnet
  properties: {
    addressPrefix: '10.0.1.0/24'
    networkSecurityGroup: {
      id: nsg.id
    }
    serviceEndpoints: [
      {
        service: 'Microsoft.KeyVault'
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the &lt;code&gt;existing&lt;/code&gt; keyword to access the &lt;code&gt;vnet&lt;/code&gt; and &lt;code&gt;nsg&lt;/code&gt;, which should already exist when the &lt;code&gt;subnet&lt;/code&gt; is being deployed. The &lt;code&gt;vnet&lt;/code&gt; is used as the &lt;code&gt;parent&lt;/code&gt; and the &lt;code&gt;nsg.id&lt;/code&gt; for the &lt;code&gt;networkSecurityGroup&lt;/code&gt;. We must also decide which IPs available on the &lt;code&gt;vnet&lt;/code&gt; should be assigned to this &lt;code&gt;subnet&lt;/code&gt;. In the example, we assigned all IPs from &lt;code&gt;10.0.1.0&lt;/code&gt; to &lt;code&gt;10.0.1.255&lt;/code&gt;. And very importantly, we need to make &lt;code&gt;Microsoft.KeyVault&lt;/code&gt; available in the &lt;code&gt;serviceEndpoints&lt;/code&gt;. Otherwise, the traffic will be blocked once again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 To be safe, ensure your related resources use the same API version. In the above, we have three &lt;code&gt;Microsoft.Network&lt;/code&gt; resources using API version &lt;code&gt;2024-03-01&lt;/code&gt;. More often than not, mixed versions will work, but sometimes you end up with bizarre behaviour. Using the same API version will increase the chance that everything works.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;And last but not least, we define the resource that has kicked off this odyssey, the Key Vault.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource vnet 'Microsoft.Network/virtualNetworks@2024-03-01' existing = {
  name: 'vnet'
}

resource subnet 'Microsoft.Network/virtualNetworks/subnets@2024-03-01' existing = {
  name: 'default'
  parent: vnet
}

resource keyVault 'Microsoft.KeyVault/vaults@2023-02-01' = {
  name: 'kv'
  location: 'eastus2'
  properties: {
    sku: {
      family: 'A'
      name: 'standard'
    }
    tenantId: subscription().tenantId
    networkAcls: {
      bypass: 'AzureServices'
      defaultAction: 'Deny'
      virtualNetworkRules: [
        {
          id: subnet.id
        }
      ]
    }
    enabledForTemplateDeployment: true
    accessPolicies: [
      {
        tenantId: subscription().tenantId
        objectId: '00000000-0000-0000-0000-000000000000'
        permissions: {
          certificates: []
          keys: []
          secrets: [
            'get'
            'list'
          ]
          storage: []
        }
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, we use the &lt;code&gt;existing&lt;/code&gt; keyword to access the already deployed &lt;code&gt;vent&lt;/code&gt; and &lt;code&gt;subnet&lt;/code&gt;. The points of interest here are the &lt;code&gt;networkAcls&lt;/code&gt; and &lt;code&gt;accessPolicies&lt;/code&gt;.&lt;br&gt;
The &lt;code&gt;defaultAction: Deny&lt;/code&gt; property of &lt;code&gt;networkAcls&lt;/code&gt; prohibits all traffic by default. With &lt;code&gt;bypass: AzureServices&lt;/code&gt; we overrides= the &lt;code&gt;defaultAction&lt;/code&gt; and allow Azure-hosted services, such as a Web App, direct access to the &lt;code&gt;kv&lt;/code&gt;. The same is true for all IPs and rules defined in the &lt;code&gt;subnet and&lt;/code&gt;nsg&lt;code&gt;, which we assign to the&lt;/code&gt;virtualNetworkRules&lt;code&gt;.&lt;br&gt;
In&lt;/code&gt;accessPolicies&lt;code&gt;we which identities (Users, Applications, Resources&lt;/code&gt; can perform which actions against Key Vault items. In the example, we grant &lt;code&gt;get/list&lt;/code&gt; access to the given &lt;code&gt;objectId&lt;/code&gt;. Where &lt;code&gt;list&lt;/code&gt; allows to fetch a list of the name of all stored secrets and &lt;code&gt;get&lt;/code&gt; allows to read the secret value.&lt;/p&gt;



&lt;p&gt;To wrap things up, let's write the pipeline we can run in Azure DevOps.&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;none&lt;/span&gt;

&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&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;TargetEnvironment&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your-azure-resource-group'&lt;/span&gt;
  &lt;span class="pi"&gt;-&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;kv01&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Vault"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="pi"&gt;-&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;vnet01&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Virtual&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Network"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="pi"&gt;-&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;nsg01&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Network&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;Group"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="pi"&gt;-&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;subnet01&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Subnet"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&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;serviceConnection&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-azure-service-connection"&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Infrastructure'&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;Infrastructure'&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;ubuntu-latest&lt;/span&gt;
    &lt;span class="c1"&gt;# Deploy kv01&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;kv01&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;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Vault'&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eq('${{ parameters.kv01 }}', &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;
      &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vnet01&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;subnet01&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="s"&gt;$(serviceConnection)&lt;/span&gt;
            &lt;span class="na"&gt;scriptType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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="s2"&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 group create \&lt;/span&gt;
              &lt;span class="s"&gt;--resource-group ${{parameters.TargetEnvironment}} \&lt;/span&gt;
              &lt;span class="s"&gt;--template-file  $(Build.SourcesDirectory)/KeyVault.bicep&lt;/span&gt;
    &lt;span class="c1"&gt;# Deploy vnet01&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;vnet01&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;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Virtual&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Network'&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eq('${{ parameters.vnet01 }}', &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="s"&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;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="s"&gt;$(serviceConnection)&lt;/span&gt;
            &lt;span class="na"&gt;scriptType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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="s2"&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 group create \&lt;/span&gt;
              &lt;span class="s"&gt;--resource-group ${{parameters.TargetEnvironment}} \&lt;/span&gt;
              &lt;span class="s"&gt;--template-file  $(Build.SourcesDirectory)/VirtualNetwork.bicep&lt;/span&gt;
    &lt;span class="c1"&gt;# Deploy nsg01&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;nsg01&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;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Network&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;Group'&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eq('${{ parameters.nsg01 }}', &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="s"&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;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="s"&gt;$(serviceConnection)&lt;/span&gt;
            &lt;span class="na"&gt;scriptType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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="s2"&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 group create \&lt;/span&gt;
              &lt;span class="s"&gt;--resource-group ${{parameters.TargetEnvironment}} \&lt;/span&gt;
              &lt;span class="s"&gt;--template-file  $(Build.SourcesDirectory)/NetworkSecurityGroup.bicep&lt;/span&gt;
    &lt;span class="c1"&gt;# Deploy subnet01&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;subnet01&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;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Subnet'&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eq('${{ parameters.subnet01 }}', &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;
      &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vnet01&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nsg01&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="s"&gt;$(serviceConnection)&lt;/span&gt;
            &lt;span class="na"&gt;scriptType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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="s2"&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 group create \&lt;/span&gt;
              &lt;span class="s"&gt;--resource-group ${{parameters.TargetEnvironment}} \&lt;/span&gt;
              &lt;span class="s"&gt;--template-file  $(Build.SourcesDirectory)/Subnet.bicep&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;trigger: -none&lt;/code&gt;, we indicate that we want to execute the pipeline manually and not, for example, as part of a PR or merge. The following block allows to select which resources should be deployed on a run and to which resource group.&lt;br&gt;
For the deployment of the resources, we have four identical blocks, one for each resource. With &lt;code&gt;condition: eq('${{ parameters.kv01 }}', true)&lt;/code&gt;, we instruct Azure to execute the job only if we have selected it. Otherwise, skip it. &lt;br&gt;
&lt;code&gt;dependsOn: - vnet01 - subnet01&lt;/code&gt; defines our dependicies. Azure will attempt to execute the jobs in a way that all dependencies are finished before the job runs.&lt;/p&gt;




&lt;p&gt;So yeah, that was our attempt to "quickly" spin up a Key Vault for the Power Platform. I hope this helps when your friend asks you for help.&lt;/p&gt;




&lt;p&gt;If you are interested in Microsoft PowerPlatform, I highly recommend to check out &lt;a href="https://dev.to/wyattdave"&gt;David's Blog&lt;/a&gt; and &lt;a href="https://powerdevbox.com/" rel="noopener noreferrer"&gt;Power DevBox&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Resources&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/virtualNetworks?pivots=deployment-language-bicep" rel="noopener noreferrer"&gt;Microsoft.Network virtualNetworks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/templates/microsoft.network/virtualnetworks/subnets?pivots=deployment-language-bicep" rel="noopener noreferrer"&gt;Microsoft.Network virtualNetworks/subnets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/templates/microsoft.network/networksecuritygroups?pivots=deployment-language-bicep" rel="noopener noreferrer"&gt;Microsoft.Network networkSecurityGroups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/templates/microsoft.keyvault/vaults?pivots=deployment-language-bicep" rel="noopener noreferrer"&gt;Microsoft.KeyVault vaults&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/customize-pipeline?view=azure-devops" rel="noopener noreferrer"&gt;Azure Pipelines - Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>powerplatform</category>
      <category>azure</category>
      <category>devops</category>
      <category>bicep</category>
    </item>
    <item>
      <title>Code Reviews: Easing the pain</title>
      <dc:creator>sandr0-p</dc:creator>
      <pubDate>Mon, 11 Nov 2024 23:58:58 +0000</pubDate>
      <link>https://dev.to/sandr0p/code-reviews-easing-the-pain-54op</link>
      <guid>https://dev.to/sandr0p/code-reviews-easing-the-pain-54op</guid>
      <description>&lt;p&gt;Most developers have met them throughout their career: Code Reviews. They are a crucial tool in software development and can help to ensure quality, consistency and maintainability. Yet, for many developers, the thought of a code review can bring a sense of dread. Why is that?&lt;/p&gt;

&lt;p&gt;While there is a plethora of reasons for code reviews being viewed so negatively, I want to focus on consistency and predictability. What do I mean by that?&lt;/p&gt;

&lt;p&gt;A code review should have roughly the same outcome, independent of who performs the review and the person's mood.&lt;/p&gt;

&lt;p&gt;In this article, I will attempt to show a path towards a more positive and efficient experience for everyone involved, turning code reviews into an opportunity for learning rather than just a necessary hurdle.&lt;/p&gt;




&lt;p&gt;The first step towards better code reviews is a shared understanding of their purpose and what a code review should entail. Without this shared understanding, every developer will focus on what they find important; thus, every code review will look different. The agreement should be as detailed as possible, preferably with examples or methodologies. It should be written down and made available in a central place.&lt;br&gt;
Here are some of the things that I think are worth looking at:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/SOLID" rel="noopener noreferrer"&gt;S.O.L.I.D Principles&lt;/a&gt; - Does the code adhere to the solid principles widely known to produce reusable code?&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://clean-code-developer.de/en/" rel="noopener noreferrer"&gt;Clean Code Development&lt;/a&gt; - Does the code follow the principles of clean code development?&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Pure_function" rel="noopener noreferrer"&gt;Pureness&lt;/a&gt; - Does the code use pure functions?&lt;/li&gt;
&lt;li&gt;Documentation - Is the code documented? In code and the technical documentation (where applicable)?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The eager reader will have noticed that the S.O.L.I.D Principles are already included in Clean Code Development. I listed them separately as I find they build a solid foundation for programming in general. (Pun intended)&lt;/p&gt;




&lt;p&gt;Every seasoned developer and reviewer will find that list a little short. I agree. It is short on purpose. Many other tasks performed in a code review can and should be automated away. Executing tests? Done in the CI/CD pipeline. Naming conventions? Done using linting.&lt;/p&gt;

&lt;p&gt;Most modern IDEs support plugins, allowing you to extend the IDE functionality based on your team's needs. You should use this capability to perform as many validations during development as possible. Many of these tools can additionally be used in CI/CD pipelines. In this way, code quality can be validated without a review, and the reviewer can focus on the aspects that actually require intelligence. It also makes a code review predictable. A reviewer shouldn't find anything if neither the IDE nor the pipeline reports any violations.&lt;/p&gt;




&lt;p&gt;Last but not least, peer reviews. I know even more time is spent on code reviews. But, if done correctly, these sessions allow developers to learn from each other, provided they are interested. Over time the teams code will become more and more similar, making it even easier to perform a review.&lt;/p&gt;




&lt;p&gt;Code reviews may never be a developer’s favourite part of the job. Still, by building consistency, using automated tools wisely, and fostering a culture of constructive feedback, we can transform code reviews into a positive experience. When reviews are predictable and focused on the most impactful elements, they become less of a chore and more of a valuable learning opportunity. By investing time in peer reviews, teams can align their code styles and principles, making future reviews more seamless and efficient. With the right approach, code reviews can strengthen code quality and team cohesion—turning an often-dreaded task into a cornerstone of continuous improvement.&lt;/p&gt;

</description>
      <category>codereview</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
