<?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: Xavier Mignot</title>
    <description>The latest articles on DEV Community by Xavier Mignot (@xaviermignot).</description>
    <link>https://dev.to/xaviermignot</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%2F188810%2F1f585c96-92db-41d9-93ad-9be8ccfa49e9.jpeg</url>
      <title>DEV Community: Xavier Mignot</title>
      <link>https://dev.to/xaviermignot</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xaviermignot"/>
    <language>en</language>
    <item>
      <title>Azure App Service, deployment slots (with configuration !) and Terraform</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Mon, 26 Dec 2022 22:34:39 +0000</pubDate>
      <link>https://dev.to/xaviermignot/azure-app-service-deployment-slots-with-configuration-and-terraform-5f5o</link>
      <guid>https://dev.to/xaviermignot/azure-app-service-deployment-slots-with-configuration-and-terraform-5f5o</guid>
      <description>&lt;p&gt;When you want to implement blue-green deployment using Azure App Services, deployment slots is the way to go. You can create a &lt;em&gt;staging&lt;/em&gt; slot to deploy the new version of your code, test it and &lt;em&gt;swap&lt;/em&gt; with the &lt;em&gt;production&lt;/em&gt; slot once ready to make the new version go live. All of this without causing downtime.&lt;br&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%2Fcjik33dnw97ba4i95ux2.gif" 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%2Fcjik33dnw97ba4i95ux2.gif" alt="Indiana Jones swap" width="480" height="204"&gt;&lt;/a&gt;&lt;br&gt;
Another main feature of App Service is configuration with &lt;em&gt;app settings&lt;/em&gt;, who are environment variables set at the service level and injected to the application code.&lt;br&gt;&lt;br&gt;
I have been using all of these for several projects over the years, but once we have started using IaC (especially Terraform), things became complicated as swap operations were messing up with the Terraform state.&lt;br&gt;&lt;br&gt;
After almost stopping using slots over the last few years, I have finally found an approach to make them work using Terraform and I'm happy to share it in this post.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By writing this post I assume you already have a good understanding of Terraform and App Service, at least you have tried to use them together 😉&lt;br&gt;&lt;br&gt;
If this sounds new to you I recommend reading my first post about Terraform: &lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/xaviermignot" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F188810%2F1f585c96-92db-41d9-93ad-9be8ccfa49e9.jpeg" alt="xaviermignot"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/xaviermignot/use-terraform-cloud-for-your-pet-projects-3dom" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Use Terraform Cloud for your pet projects&lt;/h2&gt;
      &lt;h3&gt;Xavier Mignot ・ Sep 12 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#terraform&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#azure&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
as well as some content on App Service (see the &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/overview" rel="noopener noreferrer"&gt;overview&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/configure-common?tabs=portal#configure-app-settings" rel="noopener noreferrer"&gt;app settings&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/deploy-best-practices#use-deployment-slots" rel="noopener noreferrer"&gt;deployment slots&lt;/a&gt; pages from the official documentation).
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The problem with deployment slots and Terraform state
&lt;/h2&gt;

&lt;p&gt;The main issue with deployment slots and Terraform is that the swap operations are usually performed outside of Terraform. After a swap not only the deployed package changes from one slot to another, the swap impacts also all the configuration (including app settings), application stack, Docker image if you are using containers, etc.&lt;br&gt;&lt;br&gt;
All of these properties are included in the Terraform state, so the the next &lt;code&gt;apply&lt;/code&gt; after a swap will try to revert the changes, probably causing a mess.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You might disagree on the &lt;em&gt;"usually performed outside of Terraform"&lt;/em&gt; part of this paragraph. I know there is a resource in the AzureRm provider to perform swaps, I have tried and abandoned it and explain why later in this post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  TL;DR: The solution, "briefly" explained
&lt;/h2&gt;

&lt;p&gt;If you are here only for the solution and don't want to check the demo and read the whole thing (which is fine), I'll try to explain my approach as briefly as I can in this section.  &lt;/p&gt;

&lt;p&gt;The solution relies on the following principles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An &lt;code&gt;active_app&lt;/code&gt; input variable to the main module whose value can be either &lt;code&gt;blue&lt;/code&gt; or &lt;code&gt;green&lt;/code&gt;. The main module also receives two app settings variables: one for the blue version of the app, one for the green one.
These 3 variables are used together by the module creating the App Services resources in the following way: the settings of the active app go to the &lt;em&gt;production&lt;/em&gt; slot, the other to the &lt;em&gt;staging&lt;/em&gt; slot.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;active_app&lt;/code&gt; variable is also set as an &lt;em&gt;output&lt;/em&gt; of the main module. This might sound silly but it's the way I've found to store the following information in the state: &lt;em&gt;What was the active app during the last &lt;code&gt;terraform apply&lt;/code&gt;, the blue one or the green one ?&lt;/em&gt; And this is a key part of the solution.&lt;/li&gt;
&lt;li&gt;The swap operations are made using the Azure CLI with the &lt;code&gt;az webapp deployment slot swap&lt;/code&gt; command.
Right after a swap, the previous &lt;code&gt;active_app&lt;/code&gt; value is retrieved from the state using the &lt;code&gt;terraform output active_app&lt;/code&gt; command. Then a &lt;code&gt;terraform apply&lt;/code&gt; is made using the &lt;em&gt;new&lt;/em&gt; value of the &lt;code&gt;active_app&lt;/code&gt; variable (which is the &lt;em&gt;opposite&lt;/em&gt; of the previous one).
This apply does not changes anything to the infrastructure, it changes the value of the &lt;code&gt;active_app&lt;/code&gt; output, thus saving in the state which version of the app is live.&lt;/li&gt;
&lt;li&gt;Before applying any change to the infrastructure that is not related to a swap, the latest value of the &lt;code&gt;active_app&lt;/code&gt; is retrieved from the state (still using the &lt;code&gt;output&lt;/code&gt; command). The same value is passed as the &lt;code&gt;active_app&lt;/code&gt; &lt;em&gt;variable&lt;/em&gt; so that the properties of the slots are not swapped by the &lt;code&gt;apply&lt;/code&gt; command.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is it, it was not really brief but those 4 points need a minimum amount of details. Still confused ? I got you covered, the rest of this post shows a demo with more in-depth details.&lt;/p&gt;
&lt;h2&gt;
  
  
  GitHub repository
&lt;/h2&gt;

&lt;p&gt;As I almost always do I have prepared a GitHub repository with a demo consisting of a simple ASP.NET web app, Terraform code and GitHub Actions for deployment: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/xaviermignot" rel="noopener noreferrer"&gt;
        xaviermignot
      &lt;/a&gt; / &lt;a href="https://github.com/xaviermignot/terraform-app-service-slots" rel="noopener noreferrer"&gt;
        terraform-app-service-slots
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A demo for using Azure App Service deployment slots using Terraform
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Azure App Service with Terraform, deployment slots and app settings&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This repository contains a demo on how to use Azure Services deployment slots with Terraform (yes, with app settings too !). It illustrates a blog post published &lt;a href="https://blog.xmi.fr/posts/terraform-app-service-slots/" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
The demo consists in :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a few Terraform files to provision an App Service and a deployment slot&lt;/li&gt;
&lt;li&gt;a simple ASP.NET web app&lt;/li&gt;
&lt;li&gt;GitHub Action workflows to
&lt;ul&gt;
&lt;li&gt;provision the Azure resources&lt;/li&gt;
&lt;li&gt;deploy the web app: the &lt;em&gt;blue&lt;/em&gt; version on the &lt;em&gt;production&lt;/em&gt; slot, and the &lt;em&gt;green&lt;/em&gt; version on the &lt;em&gt;staging&lt;/em&gt; slot&lt;/li&gt;
&lt;li&gt;swap the &lt;em&gt;staging&lt;/em&gt; slot with the &lt;em&gt;production&lt;/em&gt; one (as many time of you want)&lt;/li&gt;
&lt;li&gt;destroy the Azure resources once you have finished to save costs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting started&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;To run this demo by yourself there is nothing to install on you machine as everything is running in the cloud. You need to configure a few things in GitHub and in Terraform Cloud.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/xaviermignot/terraform-app-service-slots" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;&lt;br&gt;
You can fork this repo and run the demo by yourself by following the instructions in the README file. Or you can stay here, I will explain how things work in the next section.
&lt;h2&gt;
  
  
  The demo, in action
&lt;/h2&gt;

&lt;p&gt;The demo consists in a simple ASP.NET application (running in an App Service of course) presenting a page that can be either blue or green.&lt;br&gt;&lt;br&gt;
The structure of the repository is very classic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;src&lt;/code&gt; folder contains the application code&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;infra&lt;/code&gt;folder contains the Terraform files&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.github/workflows&lt;/code&gt; folder contains the GitHub Actions workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;I took this demo as an opportunity to level-up my GitHub Actions skills but I won't focus on it, as everything can be done locally or from any CI/CD solution.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Provision resources, and deploy code in both slots
&lt;/h3&gt;

&lt;p&gt;Let's start when the &lt;em&gt;blue&lt;/em&gt; version of the application is already running in production, and the &lt;em&gt;green&lt;/em&gt; one being tested in the staging slot.&lt;br&gt;
To fast-forward to this situation, I run the &lt;code&gt;initial-deployment&lt;/code&gt; workflow of the demo to perform the following actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using Terraform, create a resource group, an App Service Plan, an App Service and a staging slot.&lt;/li&gt;
&lt;li&gt;Checkout the &lt;code&gt;blue&lt;/code&gt; tag of the repo, build the code and deploy the package to the &lt;em&gt;production&lt;/em&gt; slot of the App Service&lt;/li&gt;
&lt;li&gt;Checkout the &lt;code&gt;green&lt;/code&gt; tag of the repo, and also build the code but deploy to the &lt;em&gt;staging&lt;/em&gt; slot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So far this is what we have in production:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-app-service-slots%2F01-blue-app-prod.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%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-app-service-slots%2F01-blue-app-prod.png" alt="The blue app in production" width="800" height="400"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;And in staging:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-app-service-slots%2F02-green-app-staging.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%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-app-service-slots%2F02-green-app-staging.png" alt="The green app in staging" width="800" height="400"&gt;&lt;/a&gt;  You see that red panel over here ? Its presence is managed using deployment slots (or sticky) settings&lt;/p&gt;
&lt;h3&gt;
  
  
  A little glimpse inside the IaC
&lt;/h3&gt;

&lt;p&gt;As you can see in the pics both versions of the app shows a value in &lt;em&gt;italic&lt;/em&gt; that comes from configuration, and a red panel only on the staging slot. This done using the &lt;code&gt;active_app&lt;/code&gt; variable declared like this in the main module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"active_app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt;
  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt; &lt;span class="err"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"green"&lt;/span&gt; &lt;span class="err"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The active_app value must be either 'blue' or 'green' (defaults to 'blue')."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the call to the &lt;code&gt;app_service&lt;/code&gt; module, this variable is passed alongside the settings for the blue and the green app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"app_service"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt;

  &lt;span class="nx"&gt;blue_app_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"EnvironmentLabel"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"This is the blue version"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;green_app_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;EnvironmentLabel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"This is the green version"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;app_service&lt;/code&gt; module, the &lt;code&gt;active_app&lt;/code&gt; variable is used which settings should be used for the production slot (which is the App Service resource itself):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_linux_web_app"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app-${var.project}-${var.app_name}"&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="nx"&gt;app_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blue_app_settings&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;green_app_settings&lt;/span&gt;

  &lt;span class="nx"&gt;sticky_settings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app_setting_names&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"IsStaging"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;sticky_settings&lt;/code&gt; block ? It is used to display the red panel only on the staging slot, whose creation also relies on the &lt;code&gt;active_app&lt;/code&gt; variable but in a slightly different way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# Store the "non-active" app settings in a local&lt;/span&gt;
  &lt;span class="nx"&gt;slot_app_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;green_app_settings&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blue_app_settings&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_linux_web_app_slot"&lt;/span&gt; &lt;span class="s2"&gt;"staging"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"staging"&lt;/span&gt;
  &lt;span class="nx"&gt;app_service_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_linux_web_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="c1"&gt;# Combine the "non-active" app settings with the sticky one&lt;/span&gt;
  &lt;span class="nx"&gt;app_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slot_app_settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IsStaging&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Let's swap !
&lt;/h3&gt;

&lt;p&gt;Swapping is done using the &lt;code&gt;azcli-swap&lt;/code&gt; workflow who first runs the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az webapp deployment slot swap &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP_NAME&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$APP_SERVICE_NAME&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And just after that it saves the newly active app in the Terraform state using this script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
&lt;span class="c"&gt;# Get the previous active app from the state...&lt;/span&gt;
&lt;span class="nv"&gt;currentActiveApp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; active_app&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# ...and "reverse" it: green turns blue, blue turns green...&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$currentActiveApp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'green'&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;newActiveApp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;newActiveApp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;
&lt;span class="c"&gt;# ...finally save the new active app in the state&lt;/span&gt;
terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt; &lt;span class="nt"&gt;-var&lt;/span&gt; &lt;span class="nv"&gt;active_app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$newActiveApp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which produces the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Changes to Outputs:
  ~ active_app &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt; -&amp;gt; &lt;span class="s2"&gt;"green"&lt;/span&gt;

You can apply this plan to save these new output values to the Terraform
state, without changing any real infrastructure.

Apply &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Resources: 0 added, 0 changed, 0 destroyed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back to the browser, the green version is now in production:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-app-service-slots%2F03-green-app-prod.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%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-app-service-slots%2F03-green-app-prod.png" alt="The green app in production" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the blue one has moved to staging:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-app-service-slots%2F04-blue-app-staging.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%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-app-service-slots%2F04-blue-app-staging.png" alt="The blue app in staging" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  In case of a rollback...
&lt;/h3&gt;

&lt;p&gt;To perform a rollback, just run the &lt;code&gt;azcli-swap&lt;/code&gt; workflow again ! Executing the same steps will put the blue version back in production, and green one in staging, just like before the first swap.&lt;br&gt;&lt;br&gt;
In the real world, this is why it's handy to keep the previous version in the staging slot: even if the new bits have been tested in staging, problems can still occur once in production so better be ready to put things back in place !&lt;/p&gt;
&lt;h3&gt;
  
  
  What about other changes to the infrastructure ?
&lt;/h3&gt;

&lt;p&gt;To apply changes not related to a swap, it's almost as simple as your average &lt;code&gt;terraform apply&lt;/code&gt;. You just need to retrieved the current value of the &lt;code&gt;active_app&lt;/code&gt; output and pass it as a variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;activeApp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; active_app&lt;span class="si"&gt;)&lt;/span&gt;
terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt; &lt;span class="nt"&gt;-var&lt;/span&gt; &lt;span class="nv"&gt;active_app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$activeApp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In the workflow code, the output is done like this: &lt;code&gt;activeApp=$(terraform show -json | jq -r '.values.outputs.active_app.value // "blue"')&lt;/code&gt;&lt;br&gt;&lt;br&gt;
This is to handle the first apply, as the &lt;code&gt;output -raw&lt;/code&gt; command returns an error if the state is empty. I have filled an &lt;a href="https://github.com/hashicorp/terraform/issues/32384" rel="noopener noreferrer"&gt;issue&lt;/a&gt; for this, any 👍 is appreciated 🤗&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A few words about the &lt;code&gt;azurerm_web_app_active_slot&lt;/code&gt; resource
&lt;/h2&gt;

&lt;p&gt;Before closing this post, I want to talk a little about another solution I had tried initially but later abandoned.&lt;br&gt;&lt;br&gt;
The AzureRm Terraform provider provides the &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/web_app_active_slot" rel="noopener noreferrer"&gt;azurerm_web_app_active_slot&lt;/a&gt; resource to perform a swap using Terraform.&lt;br&gt;&lt;br&gt;
There is even a &lt;a href="https://learn.microsoft.com/en-us/azure/developer/terraform/provision-infrastructure-using-azure-deployment-slots" rel="noopener noreferrer"&gt;page&lt;/a&gt; on Microsoft Learn that explains how to use it and has inspired me to write this post.&lt;br&gt;&lt;br&gt;
Initially I had built my demo using this resource like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_web_app_active_slot"&lt;/span&gt; &lt;span class="s2"&gt;"active_slot"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"green"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="nx"&gt;slot_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_linux_web_app_slot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;staging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it worked: applying the configuration with the &lt;code&gt;active_app&lt;/code&gt; set as &lt;code&gt;green&lt;/code&gt; did perform a swap.&lt;br&gt;&lt;br&gt;
Things got weird when I tried to rollback: applying again with &lt;code&gt;active_app&lt;/code&gt; as blue removed the &lt;code&gt;active_slot&lt;/code&gt; resource from the state, but it did not make a swap in the other way. Actually I had to apply the configuration again with &lt;code&gt;active_app&lt;/code&gt; as &lt;em&gt;green&lt;/em&gt; to make the &lt;em&gt;blue&lt;/em&gt; app active again 😖&lt;br&gt;&lt;br&gt;
And it went crazier when I put app settings in the mix: I always ended up with the blue app settings attached to the green app, and vice-versa...&lt;br&gt;&lt;br&gt;
Finally I think that this resource performs an API call to make the swap, aka a one-shot &lt;em&gt;operation&lt;/em&gt; that will make changes on the infrastructure &lt;em&gt;described&lt;/em&gt; else-where in the code-base.&lt;br&gt;&lt;br&gt;
The result is a mix (a mess ?) of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an &lt;em&gt;imperative&lt;/em&gt; approach, aka this &lt;code&gt;active_slot&lt;/code&gt; resource who tells &lt;em&gt;"do this swap operation !"&lt;/em&gt; &lt;/li&gt;
&lt;li&gt;a &lt;em&gt;declarative&lt;/em&gt; approach , aka the rest of my Terraform code-base, who tells &lt;em&gt;"hey I need this stuff but I let you do your thing to make it happen"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And I prefer not to mix both approaches, that's why I ended up separating them and notifying the &lt;em&gt;declarative&lt;/em&gt; stuff (the Terraform state) of the changes made by the &lt;em&gt;imperative&lt;/em&gt; stuff (the swap with az cli).  Damned I really hope this last sentence makes sense 😅&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Getting to the end of this post has been quite a ride, the writing went pretty smooth but once again I have probably spent way too much time on this demo. But I'm happy with the result, I hope this provide &lt;em&gt;one&lt;/em&gt; approach that everyone can use as a starting point to combine the benefits of deployment slots with Terraform.&lt;br&gt;&lt;br&gt;
I have learned a ton doing this, as I had barely touched GitHub Actions before. There was also a few &lt;em&gt;gotchas&lt;/em&gt; in Bash scripting, and I can't remember the last time I built even the simplest webpage without a frontend framework...&lt;br&gt;&lt;br&gt;
Thanks for reading, feel free to reach out to me if you need, and happy coding 🤓&lt;/p&gt;

</description>
      <category>azure</category>
      <category>terraform</category>
      <category>appservice</category>
    </item>
    <item>
      <title>Deploy Azure Logic Apps as code</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Fri, 26 Aug 2022 13:56:01 +0000</pubDate>
      <link>https://dev.to/xaviermignot/deploy-azure-logic-apps-as-code-2jfm</link>
      <guid>https://dev.to/xaviermignot/deploy-azure-logic-apps-as-code-2jfm</guid>
      <description>&lt;p&gt;I have been playing with Azure Logic Apps on my own lately, and was wondering how these can be managed by teams in an enterprise environment, especially how can we automate the provisioning and deployment of Logic Apps.&lt;br&gt;&lt;br&gt;
In this post I will show how we can do this using infrastructure as code using Bicep.&lt;/p&gt;
&lt;h2&gt;
  
  
  Presentation of the context
&lt;/h2&gt;

&lt;p&gt;As an example we are going to build a very simple project consisting in the following elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Storage Account&lt;/li&gt;
&lt;li&gt;A table in the Storage Account&lt;/li&gt;
&lt;li&gt;A Logic App triggered by HTTP: each request will add a line in the storage table with the IP of the caller&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---92GA8gt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/logic-apps-iac/01-diagram.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---92GA8gt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/logic-apps-iac/01-diagram.png" alt="Diagram" width="880" height="288"&gt;&lt;/a&gt; &lt;em&gt;The following diagram shows what we are going to create here&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  GitHub repository
&lt;/h2&gt;

&lt;p&gt;I have prepared a &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/"&gt;GitHub repository&lt;/a&gt; to let you see the whole code of the demo, and eventually run it by yourself.&lt;br&gt;&lt;br&gt;
The repo contains the full IaC code in Bicep and Terraform, in the post I will show the most relevant parts in Bicep only for clarity reasons (and also because it's all little bit more complicated in Terraform, more on this later).&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating the base resources
&lt;/h2&gt;

&lt;p&gt;To get started we are going to create all the resources but the Logic App itself. If you're not familiar with Bicep this will help to understand the rest of the code, otherwise you can jump to the next section.&lt;br&gt;&lt;br&gt;
Note that my naming convention is composed of the same &lt;em&gt;suffix&lt;/em&gt; in all resources names (with a random part to ensure the unicity of the name), and a &lt;em&gt;prefix&lt;/em&gt; depending on the resource type.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can use &lt;a href="https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations"&gt;this page&lt;/a&gt; from the Cloud Adoption Framework as a reference for the abbreviations for Azure resource types.&lt;br&gt;&lt;br&gt;
This &lt;a href="https://justinoconnor.codes/2022/08/19/azure-periodic-table-of-resource-naming-convention-shorthands/"&gt;periodic table of Azure resource types&lt;/a&gt; is also pretty neat.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm new to Bicep but already used to create a &lt;code&gt;main.bicep&lt;/code&gt; module containing a &lt;em&gt;subscription&lt;/em&gt; deployment to create the resource group, and a &lt;code&gt;resources.bicep&lt;/code&gt; module containing a &lt;em&gt;resource group&lt;/em&gt; deployment.&lt;br&gt;&lt;br&gt;
I will focus on the &lt;code&gt;resources.bicep&lt;/code&gt; module here, here is the beginning of the module with the creation of the Storage Account, and the storage table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@description('The suffix to use in resource naming.')
param suffix string
param location string

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = {
  name: substring('stor${replace(suffix, '-', '')}', 0, 24)
  location: location

  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
}

resource tableStorageService 'Microsoft.Storage/storageAccounts/tableServices@2021-09-01' = {
  name: 'default'
  parent: storageAccount
}

resource storageTable 'Microsoft.Storage/storageAccounts/tableServices/tables@2021-09-01' = {
  name: 'logicAppCalls'
  parent: tableStorageService
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the API Connection
&lt;/h2&gt;

&lt;p&gt;To interact with a resource or a service, a Logic App requires an &lt;em&gt;API connection&lt;/em&gt;, which is basically a wrapper around an API, the Azure table storage API in our case.&lt;br&gt;&lt;br&gt;
This is one of the key steps of this post, as the service connection contains the way the Logic App authenticates to the Storage Account. I could have used an account key but decided to use a &lt;em&gt;managed identity&lt;/em&gt; to follow best practices (&lt;a href="https://medium.com/medialesson/deploying-azure-logic-apps-managed-identity-with-bicep-e1354f185e4d"&gt;this post&lt;/a&gt; helped me a lot to make this part work).  &lt;/p&gt;

&lt;p&gt;Long story short, the trick with the Managed Identity is to use the &lt;code&gt;2018-07-01-preview&lt;/code&gt; API version of the &lt;code&gt;Microsoft.Web/connections&lt;/code&gt; resource type. Bicep will show a warning because of this but it's currently the only way to make it work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource tableStorageConnection 'Microsoft.Web/connections@2018-07-01-preview' = {
  name: 'tableStorage'
  location: location

  properties: {
    parameterValueSet: {
      name: 'managedIdentityAuth'
      values: {}
    }
    api: {
      id: '${subscription().id}/providers/Microsoft.Web/locations/${location}/managedApis/azuretables'
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the Logic App workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why are Logic Apps &lt;em&gt;different&lt;/em&gt; ?
&lt;/h3&gt;

&lt;p&gt;Before jumping into some more IaC code, let's talk about how teams could manage (develop, version and deploy) Logic Apps and why they are different from other Azure resources like Web Apps or Azure Functions.&lt;br&gt;&lt;br&gt;
Functions and Web Apps are code-first services whose deployment require two steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A provisioning step to create the resource in Azure (using IaC, the CLI, the portal, ...)&lt;/li&gt;
&lt;li&gt;A deployment step to push business code to the resource (using a CD pipeline, VS Code,  a git push, ...)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Basically an empty shell is created, and then content is sent into the shell. Both steps can be achieved by different teams using different technologies.&lt;br&gt;&lt;br&gt;
Also deploying a new version of the business code does not reflect any change on the infrastructure.&lt;/p&gt;

&lt;p&gt;Logic apps is a design-first service, typically you design your workflow in a GUI (like the Azure portal), and it is saved in a JSON "code" tied to the Azure resource.&lt;br&gt;&lt;br&gt;
So there is not the same separation between provisioning and deployment as above: any change to the business logic will be reflected on the infrastructure.&lt;br&gt;&lt;br&gt;
In other words, the "code" or the "logic" of the Logic Apps cannot be deployed separately from the creation of the resource itself.  &lt;/p&gt;
&lt;h3&gt;
  
  
  Let's develop/deploy this Logic App for good now !
&lt;/h3&gt;

&lt;p&gt;So how can we develop Logic Apps from the GUI and automate their deployment using IaC ?&lt;br&gt;&lt;br&gt;
Here is what I've got in the Azure portal once I've finished "developing" my Logic App:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kDjsB-jC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/logic-apps-iac/02-workflow-portal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kDjsB-jC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/logic-apps-iac/02-workflow-portal.png" alt="Logic App workflow in Azure portal" width="880" height="455"&gt;&lt;/a&gt; &lt;em&gt;A similar experience is available using the VS Code extension&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Switching to the &lt;em&gt;Code view&lt;/em&gt; allows me to see my workflow in JSON. From there I can copy the &lt;code&gt;definition&lt;/code&gt; element and save its content a &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/blob/81f3560869aaf70bd945132e63b7034833216403/logic_apps/insertIntoTableStorage.json"&gt;file&lt;/a&gt; in my repo.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why not taking &lt;em&gt;all&lt;/em&gt; the content of the code view ? Because the &lt;code&gt;parameters&lt;/code&gt; element contains the resource id of the API connection (with the subscription id and resource group name) and the Storage Account name, and I don't want these kind of environment-related information to end up in my git repository.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that my logic is saved (and versioned) alongside my IaC code, I can use it in the &lt;code&gt;definition&lt;/code&gt; property of my Logic App workflow (using the &lt;code&gt;loadJsonContent&lt;/code&gt; builtin function):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource logicApp 'Microsoft.Logic/workflows@2019-05-01' = {
  name: 'ala-${suffix}'
  location: location

  identity: {
    type: 'SystemAssigned'
  }

  properties: {
    definition: loadJsonContent('../logic_apps/insertIntoTableStorage.json')
    parameters: {
      '$connections': {
        value: {
          '${connectionApiName}': {
            connectionId: tableStorageConnection.id
            connectionName: connectionApiName
            id: '${subscription().id}/providers/Microsoft.Web/locations/${location}/managedApis/${connectionApiName}'
            connectionProperties: {
              authentication: {
                type: 'ManagedServiceIdentity'
              }
            }
          }
        }
      }
      storageAccountName: {
        value: storageAccount.name
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only thing left to do is to grant the Logic App access to the Storage Account using an assignment to the &lt;code&gt;Storage Table Data Contributor&lt;/code&gt; builtin role. This can be seen &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/blob/81f3560869aaf70bd945132e63b7034833216403/bicep/resources.bicep#L73-L83"&gt;here&lt;/a&gt; in the repository.  &lt;/p&gt;

&lt;p&gt;Finally everything can be deployed using a simple &lt;code&gt;az deployment sub create&lt;/code&gt; command.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about Terraform ?
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, this little demo project has been done in Bicep and Terraform, but I ended up keeping only the Bicep version in the blog post. This is for clarity reasons but also because doing this with Terraform has the following drawbacks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There is no support for API connections in the AzureRM Terraform provider, so I had to create &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/blob/81f3560869aaf70bd945132e63b7034833216403/terraform/apiConnectionArm.json"&gt;an ARM template&lt;/a&gt; and &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/blob/81f3560869aaf70bd945132e63b7034833216403/terraform/main.tf#L30-L35"&gt;invoke it&lt;/a&gt; in my Terraform code to create the API connection&lt;/li&gt;
&lt;li&gt;A Logic App workflow can be created using the &lt;code&gt;logic_app_workflow&lt;/code&gt; resource of the AzureRM provider, but it doesn't provide a way to set the definition of the workflow. The solution was to use this resource to create the workflow (with the Managed Identity for later role assignment), and then another ARM template &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/blob/81f3560869aaf70bd945132e63b7034833216403/terraform/logicAppWorkflowArm.json"&gt;file&lt;/a&gt; to deploy the definition of the Logic App from the Terraform &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/blob/81f3560869aaf70bd945132e63b7034833216403/terraform/main.tf#L59-L74"&gt;code&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;This post shows a way to achieve a first step in managing Logic Apps &lt;em&gt;at scale&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
There are still many things to cover such as how to run the deployments in a pipeline, how to make a change and bring it from development to production, how to allow changes using the GUI only in development and force the use of pipelines in other environments, etc.&lt;br&gt;&lt;br&gt;
I hope this post will help you to bring your Logic Apps to the next level, do not hesitate to reach out if you need, and thanks for reading 🤓&lt;/p&gt;

</description>
      <category>azure</category>
      <category>bicep</category>
      <category>logicapps</category>
      <category>iac</category>
    </item>
    <item>
      <title>TLS with Terraform and Azure: use managed certificates</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Fri, 15 Apr 2022 19:40:28 +0000</pubDate>
      <link>https://dev.to/xaviermignot/tls-with-terraform-and-azure-use-managed-certificates-1bfn</link>
      <guid>https://dev.to/xaviermignot/tls-with-terraform-and-azure-use-managed-certificates-1bfn</guid>
      <description>&lt;p&gt;This post is the last one of my series on the generation of TLS certificates with Terraform for Azure, after the post about &lt;a href="https://dev.to/xaviermignot/tls-with-terraform-and-azure-generate-self-signed-certificates-2f83"&gt;self signed certificates&lt;/a&gt; and the one about &lt;a href="https://dev.to/xaviermignot/tls-with-terraform-and-azure-get-certificates-from-lets-encrypt-3dg9"&gt;Let's Encrypt&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
For this one we are going to let Azure &lt;em&gt;manage&lt;/em&gt; everything by using &lt;em&gt;managed certificates&lt;/em&gt;, a feature available on several services that let Azure handle the generation and the renewal of certificates.&lt;/p&gt;
&lt;h2&gt;
  
  
  Previously in the "TLS with Terraform and Azure" series...
&lt;/h2&gt;

&lt;p&gt;If you haven't read the previous posts of the series you can check out &lt;a href="https://dev.to/xaviermignot/tls-with-terraform-and-azure-generate-self-signed-certificates-2f83/#presentation-of-the-context-for-the-whole-series"&gt;this section&lt;/a&gt; of the first one to get the common context for the whole series.  &lt;/p&gt;

&lt;p&gt;There is also a GitHub repository that contains all the code from this series of posts: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/xaviermignot"&gt;
        xaviermignot
      &lt;/a&gt; / &lt;a href="https://github.com/xaviermignot/terraform-certificates"&gt;
        terraform-certificates
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A repository showing how to generate certificates using Terraform
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Certificate generation with Terraform for Azure App Service&lt;/h1&gt;
&lt;p&gt;This repository contains sample code to generate TLS certificates using Terraform.&lt;br&gt;
It uses an Azure App Service as an example of a website to secure.&lt;/p&gt;
&lt;p&gt;The certificates are generated in 3 ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;By creating a self-signed certificate&lt;/li&gt;
&lt;li&gt;By requesting a certificate from Let's Encrypt&lt;/li&gt;
&lt;li&gt;By creating an Azure App Service managed certificate&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Obviously the third example can only work with Azure. The two others are written to work with Azure as well but can be adapted or used as an inspiration to work on other platforms.&lt;/p&gt;
&lt;h2&gt;
Getting started&lt;/h2&gt;
&lt;h3&gt;
Set the variables of the root module&lt;/h3&gt;
&lt;p&gt;If you want to run this against your Azure infrastructure you will need to provide values for the variables of the root module:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;dns_zone_name&lt;/code&gt; should contain the name of your DNS Zone managed in Azure&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;dns_zone_rg_name&lt;/code&gt; should contain the name of the resource group containing…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/xaviermignot/terraform-certificates"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;To give some context very quickly, let's say we have used the &lt;code&gt;app_service&lt;/code&gt; module of the GitHub repo to create an App Service bound to a custom domain, and now we need to secure this custom domain using a certificate.&lt;br&gt;&lt;br&gt;
We have already done this using a self-signed certificate, and with a Let's Encrypt certificate, now we are doing it using a managed one.&lt;/p&gt;
&lt;h2&gt;
  
  
  Level 3: let Azure handle the certificate stuff
&lt;/h2&gt;

&lt;p&gt;The idea behind managed certificates is pretty simple: if you can prove you &lt;em&gt;own&lt;/em&gt; a domain, then Azure will issue a certificate for you, valid for this domain.&lt;br&gt;&lt;br&gt;
And then you don't have to worry about it, Azure will renew the certificate for you, you can forget about it and focus on other things, like your business for instance...&lt;/p&gt;

&lt;p&gt;On the diagram side, things looks way simpler than for the other "levels":&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gcyr_vxu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/azure-terraform-certificates/04-managed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gcyr_vxu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/azure-terraform-certificates/04-managed.png" alt="Diagram" width="880" height="414"&gt;&lt;/a&gt; &lt;em&gt;The diagram looks almost the same as for self-signed certificates&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On the code side, we have previously bound the App Service to a custom domain using a &lt;code&gt;azurerm_app_service_custom_hostname_binding&lt;/code&gt; resource in the &lt;code&gt;app_service&lt;/code&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Bind app service to custom domain&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_app_service_custom_hostname_binding"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;hostname&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tf-certs-demo.${var.dns.zone_name}"&lt;/span&gt;
  &lt;span class="nx"&gt;app_service_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_app_service&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;azurerm_dns_txt_record&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the &lt;code&gt;managed&lt;/code&gt; module, creating the managed is done using the &lt;code&gt;azurerm_app_service_managed_certificate&lt;/code&gt; with only the &lt;em&gt;binding id&lt;/em&gt; as an argument:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_app_service_managed_certificate"&lt;/span&gt; &lt;span class="s2"&gt;"managed"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;custom_hostname_binding_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_domain_binding_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And finally, back in the &lt;code&gt;main&lt;/code&gt; module, the &lt;code&gt;azurerm_app_service_certificate_binding&lt;/code&gt; resource brings everything together by binding the managed certificate to the custom domain:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_app_service_certificate_binding"&lt;/span&gt; &lt;span class="s2"&gt;"cert_binding"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;certificate_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;managed&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;certificate_id&lt;/span&gt;
  &lt;span class="nx"&gt;hostname_binding_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_service&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_domain_binding_id&lt;/span&gt;
  &lt;span class="nx"&gt;ssl_state&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SniEnabled"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note that comparing to the other posts of the series, I have detailed a little bit more what's happening in the code before and after the generation of the certificate. Otherwise I would have only 3 lines of code to show 😬  &lt;/p&gt;

&lt;p&gt;The code snippets above are spread into 3 modules from the repo of the whole series.&lt;br&gt;&lt;br&gt;
I had previously made another repo dedicated to managed certificates using a single module if you prefer: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/xaviermignot"&gt;
        xaviermignot
      &lt;/a&gt; / &lt;a href="https://github.com/xaviermignot/app-service-managed-certificate-demo"&gt;
        app-service-managed-certificate-demo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Azure App Service Managed Certificate demo&lt;/h1&gt;
&lt;p&gt;This repository contains a simple demo of App Service managed certificates with Terraform.&lt;/p&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/xaviermignot/app-service-managed-certificate-demo"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;When browsing the App Service, we can see that the certificate is issued by GeoTrust (aka DigiCert) and of course trusted by the browser: &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pa6iDfTz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/azure-terraform-certificates/04-managed-browser.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pa6iDfTz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/azure-terraform-certificates/04-managed-browser.png" alt="Browser" width="473" height="665"&gt;&lt;/a&gt; &lt;em&gt;The certificate is trusted and valid for 6 months ✅&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Managed certificates: too good to be true ?
&lt;/h2&gt;

&lt;p&gt;As you can see it's very simple to generate a managed certificate, and it's &lt;del&gt;free&lt;/del&gt; included in the App Service Plan's pricing. It looks like the perfect solution for securing your Web Apps, but there is a downside that you should be aware of: if you want to use managed certificates, &lt;strong&gt;your App Service have to be publicly exposed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So you can't use them if you put your Web Apps behind an appliance like an Application Gateway and enforce the traffic to go through this appliance. &lt;br&gt;
Even if it's technically feasible to do so (by using tricky maneuvers, I have done this but I won't tell more 😅), this is not a target solution as it will prevent the certificate from being renewed by App Service.&lt;/p&gt;

&lt;p&gt;There are other minor limitations like the lack of support of wildcard certificates and the impossibility to export the certificate but it seems fair to me and it did not bother me at all in my personal usage.&lt;/p&gt;

&lt;p&gt;These limitations are listed &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/configure-ssl-certificate?tabs=subdomain%2Cportal#create-a-free-managed-certificate"&gt;here&lt;/a&gt; in the documentation, a little bit quietly so I think it's worth mentioning them.&lt;/p&gt;

&lt;p&gt;To finish on a positive note, these limitations mean that managed certificate might not suit enterprise scenarios where we will probably put a security appliance in front of our App Services.&lt;br&gt;&lt;br&gt;
But for personal projects, or smaller ones, proof of concepts, or sandbox environments, it's really a great feature, as it makes really easy to expose you applications securely on a custom domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managed certificates for other Azure services
&lt;/h3&gt;

&lt;p&gt;Staying on a positive track before closing this post, I have to mention that managed certificates are not limited to App Service in Azure.   &lt;/p&gt;

&lt;p&gt;I have already used them for Azure &lt;a href="https://docs.microsoft.com/en-us/azure/cdn/cdn-custom-ssl?tabs=option-1-default-enable-https-with-a-cdn-managed-certificate#tlsssl-certificates"&gt;CDN&lt;/a&gt; (in GA, supported by the AzureRm Terraform provider) and Azure &lt;a href="https://docs.microsoft.com/en-us/azure/api-management/configure-custom-domain?tabs=managed#domain-certificate-options"&gt;API Management&lt;/a&gt; (currently in public preview, no support in Terraform yet).  &lt;/p&gt;

&lt;p&gt;It might also exist for other services so if your are about to use an Azure service associated with a custom domain, look for the support of managed certificates 😉&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;As this post closes the series on certificate generation with Terraform for Azure, let's recap the whole series in a single table:  &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Generation method&lt;/th&gt;
&lt;th&gt;Ease of use&lt;/th&gt;
&lt;th&gt;Security level&lt;/th&gt;
&lt;th&gt;Summary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Self-signed certificates&lt;/td&gt;
&lt;td&gt;✅✅&lt;/td&gt;
&lt;td&gt;🔓&lt;/td&gt;
&lt;td&gt;You can start here but consider moving to the other method as soon as you can.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Let's Encrypt&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;🔐🔐🔐&lt;/td&gt;
&lt;td&gt;Free &amp;amp; trusted certs, require little maintenance once set-up. &lt;br&gt; A little harder to understand at first but then can be used everywhere.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed certificates&lt;/td&gt;
&lt;td&gt;✅✅✅&lt;/td&gt;
&lt;td&gt;🔐🔐🔐&lt;/td&gt;
&lt;td&gt;Use this unless your Web Apps can't be directly and publicly accessible. &lt;br&gt; Or if you are not using Web Apps...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's it for this post, I hope this series has been informative, as this is not the topic I am the most comfortable with (don't forget that I am a developer first, and most of us are scared by certificates 😱).  &lt;/p&gt;

&lt;p&gt;Anyway, there are several ways to automate certificate generation in Azure and in general, and the industry makes it easier that ever, through open initiatives like Let's Encrypt or public cloud specific features like managed certificates.  &lt;/p&gt;

&lt;p&gt;Choose the one that best suits your needs, and thanks for reading 🤓&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>azure</category>
      <category>appservice</category>
    </item>
    <item>
      <title>TLS with Terraform and Azure: get certificates from Let's Encrypt</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Wed, 30 Mar 2022 12:04:27 +0000</pubDate>
      <link>https://dev.to/xaviermignot/tls-with-terraform-and-azure-get-certificates-from-lets-encrypt-3dg9</link>
      <guid>https://dev.to/xaviermignot/tls-with-terraform-and-azure-get-certificates-from-lets-encrypt-3dg9</guid>
      <description>&lt;p&gt;Following my &lt;a href="https://dev.to/xaviermignot/tls-with-terraform-and-azure-generate-self-signed-certificates-2f83"&gt;previous post&lt;/a&gt; on generating self-signed certificates with Terraform, this one is the second post of the series.&lt;br&gt;&lt;br&gt;
This time we are going to use Let's Encrypt as the certificate authority (CA) instead of our own machine. As a result we will get trusted certificates that can be used in production, for free.&lt;/p&gt;
&lt;h2&gt;
  
  
  Previously in the "TLS with Terraform and Azure" series...
&lt;/h2&gt;

&lt;p&gt;If you haven't read my previous post you can check out &lt;a href="https://dev.to/xaviermignot/tls-with-terraform-and-azure-generate-self-signed-certificates-2f83//#presentation-of-the-context-for-the-whole-series"&gt;this section&lt;/a&gt; to get the common context for the whole series.&lt;br&gt;&lt;br&gt;
There is also a GitHub repository that contains all the code from this series of posts: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/xaviermignot"&gt;
        xaviermignot
      &lt;/a&gt; / &lt;a href="https://github.com/xaviermignot/terraform-certificates"&gt;
        terraform-certificates
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A repository showing how to generate certificates using Terraform
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Certificate generation with Terraform for Azure App Service&lt;/h1&gt;
&lt;p&gt;This repository contains sample code to generate TLS certificates using Terraform.&lt;br&gt;
It uses an Azure App Service as an example of a website to secure.&lt;/p&gt;
&lt;p&gt;The certificates are generated in 3 ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;By creating a self-signed certificate&lt;/li&gt;
&lt;li&gt;By requesting a certificate from Let's Encrypt&lt;/li&gt;
&lt;li&gt;By creating an Azure App Service managed certificate&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Obviously the third example can only work with Azure. The two others are written to work with Azure as well but can be adapted or used as an inspiration to work on other platforms.&lt;/p&gt;
&lt;h2&gt;
Getting started&lt;/h2&gt;
&lt;h3&gt;
Set the variables of the root module&lt;/h3&gt;
&lt;p&gt;If you want to run this against your Azure infrastructure you will need to provide values for the variables of the root module:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;dns_zone_name&lt;/code&gt; should contain the name of your DNS Zone managed in Azure&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;dns_zone_rg_name&lt;/code&gt; should contain the name of the resource group containing…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/xaviermignot/terraform-certificates"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Level 2: requesting a certificate from Let's Encrypt
&lt;/h2&gt;

&lt;p&gt;If you don't know what Let's Encrypt is, in a few words it's a free, automated and open certificate authority (CA), allowing everyone to get certificates trusted by browsers at no charge.&lt;br&gt;&lt;br&gt;
As the Let's Encrypt certificates are valid for 90 days (instead of one year for commercial authorities), automation is strongly recommended.&lt;br&gt;&lt;br&gt;
So how can we automate this with Terraform ? Using the third-party provider &lt;a href="https://registry.terraform.io/providers/vancluever/acme/latest/docs"&gt;ACME&lt;/a&gt;. ACME stands for &lt;em&gt;Automated Certificate Management Environment&lt;/em&gt;, the protocol used by Let's Encrypt. The Terraform ACME provider supports any ACME CA, so we need to configure Let's Encrypt's endpoint in the provider configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# The provider is declared here just like any provider...&lt;/span&gt;
    &lt;span class="nx"&gt;acme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vancluever/acme"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# ...and is configured here, with the Let's Encrypt production endpoint.&lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"acme"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://acme-v02.api.letsencrypt.org/directory"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Let's Encrypts provides a staging endpoint you should use for testing: &lt;code&gt;https://acme-staging-v02.api.letsencrypt.org/directory&lt;/code&gt;&lt;br&gt;&lt;br&gt;
There is a limit of 50 certificates per domain and per week on the production environment, on the staging it's 30,000 (but the certificates will not be trusted by the browser)&lt;br&gt;
{: .prompt-tip }  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once the provider properly configured, we can start creating resources. As for self-signed certificates, it starts here with a private key, and using this private key and an email we create a &lt;em&gt;registration&lt;/em&gt; which is an account on the ACME server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Creates a private key in PEM format&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"tls_private_key"&lt;/span&gt; &lt;span class="s2"&gt;"private_key"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;algorithm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"RSA"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Creates an account on the ACME server using the private key and an email&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"acme_registration"&lt;/span&gt; &lt;span class="s2"&gt;"reg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;account_key_pem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tls_private_key&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key_pem&lt;/span&gt;
  &lt;span class="nx"&gt;email_address&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can request a certificate using our account, this is where the real magic happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# As the certificate will be generated in PFX a password is required&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_password"&lt;/span&gt; &lt;span class="s2"&gt;"cert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;
  &lt;span class="nx"&gt;special&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Gets a certificate from the ACME server&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"acme_certificate"&lt;/span&gt; &lt;span class="s2"&gt;"cert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;account_key_pem&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;acme_registration&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reg&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account_key_pem&lt;/span&gt;
  &lt;span class="nx"&gt;common_name&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;common_name&lt;/span&gt; &lt;span class="c1"&gt;# The hostname goes here&lt;/span&gt;
  &lt;span class="nx"&gt;certificate_p12_password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;random_password&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;

  &lt;span class="nx"&gt;dns_challenge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Many providers are supported for the DNS challenge, we are using Azure DNS here&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"azure"&lt;/span&gt;

    &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# Some arguments are passed here but it's not enough to let the provider access the zone in Azure DNS.&lt;/span&gt;
      &lt;span class="c1"&gt;# Other arguments (tenant id, subscription id, and cient id/secret) must be set through environment variables.&lt;/span&gt;
      &lt;span class="nx"&gt;AZURE_RESOURCE_GROUP&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dns&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_rg_name&lt;/span&gt;
      &lt;span class="nx"&gt;AZURE_ZONE_NAME&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dns&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_name&lt;/span&gt;
      &lt;span class="nx"&gt;AZURE_TTL&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key part is in the &lt;code&gt;dns_challenge&lt;/code&gt; block of the &lt;code&gt;acme_certificate&lt;/code&gt; resource.  &lt;/p&gt;

&lt;p&gt;Before generating a certificate for our domain, Let's Encrypt checks that we &lt;em&gt;own&lt;/em&gt; that domain. To do that it proceeds with a DNS &lt;em&gt;challenge&lt;/em&gt;, basically it generates a random string and will not generate the certificate unless that random string is in a specific TXT record of the DNS zone.  &lt;/p&gt;

&lt;p&gt;Obviously the ACME provider does that for us, we just need to let it access our DNS zone. The provider supports &lt;em&gt;many&lt;/em&gt; DNS providers, in this case we are using Azure DNS.  &lt;/p&gt;

&lt;p&gt;So we need to tell the ACME provider &lt;em&gt;where&lt;/em&gt; our DNS zone is, so we specify its name and resource group name in the &lt;code&gt;config&lt;/code&gt; block above. Unfortunately the provider cannot use the Azure CLI authentication so we need to set additional arguments for authentication, so that the provider knows the subscription we are using, and a client id/secret to access it.  &lt;/p&gt;

&lt;p&gt;This is the kind of information that should not be pushed to a git repository, so I prefer to put these as environment variables in a &lt;code&gt;.env&lt;/code&gt; git-ignored file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ARM_TENANT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;YOUR AZURE TENAND ID (a guid)&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ARM_SUBSCRIPTION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;YOUR AZURE SUBSCRIPTION ID (another guid)&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ARM_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;AN APP REGISTRATION ID (yep it's a guid too)&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ARM_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;THE APP REGISTRATION SECRET (not a guid this time)&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I use the &lt;code&gt;source .env&lt;/code&gt; command and the ACME provider can use the environment variables to authenticate to Azure and add/remove records in my DNS zone.  &lt;/p&gt;

&lt;p&gt;If you are using Terraform Cloud or running Terraform in a CI/CD context you will not need to do this as the environment variables are already set&lt;/p&gt;

&lt;p&gt;Once the &lt;a href="https://github.com/xaviermignot/terraform-certificates/blob/main/02_acme/main.tf"&gt;full code&lt;/a&gt; has been applied you can check out the Activity Log of your DNS zone.&lt;br&gt;&lt;br&gt;
You should see that the service principal corresponding to the environment variables has created and deleted a TXT record in the zone :&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oE_HJllS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/azure-terraform-certificates/03-lets-encrypt-activity-log.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oE_HJllS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/azure-terraform-certificates/03-lets-encrypt-activity-log.png" alt="Activity Log" width="880" height="94"&gt;&lt;/a&gt; &lt;em&gt;The creation and deletion of the TXT record should occur within a minute&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And more importantly you can browse your App Service from its custom hostname and see how your browser enjoys this fresh trusted certificate:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pLmAkWyE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/azure-terraform-certificates/03-lets-encrypt-browser.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pLmAkWyE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/azure-terraform-certificates/03-lets-encrypt-browser.png" alt="Browser" width="834" height="1058"&gt;&lt;/a&gt; &lt;em&gt;Note the R3 issuer which is Let's Encrypt, and the absence of security warning ✅&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;That's if for this post, which is the one why I have started this series. At first I had trouble understanding how this Let's Encrypt/ACME provider works and didn't find any content showing a full example. &lt;br&gt;
So I hope this post makes sense and helps other to issue trusted certificates using Terraform.  &lt;/p&gt;

&lt;p&gt;Don't hesitate to dive into Let's Encrypt documentation as well, they explain in detail how the &lt;a href="https://letsencrypt.org/docs/challenge-types/#dns-01-challenge"&gt;DNS challenge&lt;/a&gt; works, as well as the &lt;a href="https://letsencrypt.org/how-it-works/"&gt;service itself&lt;/a&gt; and their &lt;a href="https://letsencrypt.org/certificates/"&gt;certificate chain&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Back to the original diagram, here is where we are now:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8FWh75ph--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/azure-terraform-certificates/03-lets-encrypt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8FWh75ph--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.xmi.fr/assets/img/azure-terraform-certificates/03-lets-encrypt.png" alt="Diagram" width="880" height="605"&gt;&lt;/a&gt; &lt;em&gt;The certificate is now issued by a trusted CA before being bounded to the App Service&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The next post will close the series with Azure &lt;em&gt;managed&lt;/em&gt; certificates, a feature that deserves more attention 😉&lt;br&gt;&lt;br&gt;
Thanks for reading !&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>azure</category>
      <category>appservice</category>
    </item>
    <item>
      <title>TLS with Terraform and Azure: generate self-signed certificates</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Fri, 18 Mar 2022 16:32:29 +0000</pubDate>
      <link>https://dev.to/xaviermignot/tls-with-terraform-and-azure-generate-self-signed-certificates-2f83</link>
      <guid>https://dev.to/xaviermignot/tls-with-terraform-and-azure-generate-self-signed-certificates-2f83</guid>
      <description>&lt;p&gt;We all love starting pet projects, so we tend to buy custom domains as it can be fairly cheap. SSL/TLS certificates on the other hand used to be pricey, but today there are several solutions to get these for free.&lt;br&gt;&lt;br&gt;
And this is for the good cause as every website should be secured by certificates nowadays.&lt;br&gt;&lt;br&gt;
This post is the first of a series where I will share 3 ways to automate the generation of certificates with Terraform for your Azure projects.&lt;br&gt;&lt;br&gt;
This one introduces the common workflow around an Azure Web App, and shows the first &lt;em&gt;level&lt;/em&gt; of certificate generation using &lt;em&gt;self-signed&lt;/em&gt; certificates.&lt;br&gt;&lt;br&gt;
The second post will use Let's Encrypt and the last one managed certificates (I will put the links here once the posts will have been published).  &lt;/p&gt;
&lt;h2&gt;
  
  
  Presentation of the context for the whole series
&lt;/h2&gt;

&lt;p&gt;Let's say we have the following architecture as a starting point:&lt;br&gt;&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%2Fdamt4zrkqwmok9xx1218.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%2Fdamt4zrkqwmok9xx1218.png" alt="Starting architecture"&gt;&lt;/a&gt; &lt;em&gt;Let's start with a Web App bound to a custom domain&lt;/em&gt;&lt;br&gt;&lt;br&gt;
So we have the following components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An App Service running in a plan with in the Basic tier at least&lt;/li&gt;
&lt;li&gt;A DNS zone with at least the following records:

&lt;ul&gt;
&lt;li&gt;A CNAME record pointing to the default App Service hostname (&lt;code&gt;*.azurewebsites.net&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A TXT records to verify the domain ownership&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;These two records allow the creation of a custom hostname &lt;em&gt;binding&lt;/em&gt; at the App Service level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is created using Terraform CLI from my terminal, including the DNS records, that's why I'm using Azure DNS 😎&lt;br&gt;&lt;br&gt;
With this setup the App Service is accessible on the custom hostname using HTTP only, if we try to access it using HTTPS our browser displays an error as the only certificate it can find is bound the &lt;code&gt;azurewebsites.net&lt;/code&gt; domain:&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%2Fy5qmmf5erke1spa2nfrl.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%2Fy5qmmf5erke1spa2nfrl.png" alt="Browser error"&gt;&lt;/a&gt; &lt;em&gt;The browser displays an error as the certificate doesn't match the hostname... yet&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;In the Azure portal, the custom domain is added to the App Service but marked as unsecured:&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%2F7uogjbod8aw8rp38nkcd.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%2F7uogjbod8aw8rp38nkcd.png" alt="Azure portal"&gt;&lt;/a&gt; &lt;em&gt;The Custom domains blade of the Azure portal displays this error on our custom domain&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now that we have our basic setup, let's see how we can secure this web app.&lt;/p&gt;
&lt;h2&gt;
  
  
  GitHub repository
&lt;/h2&gt;

&lt;p&gt;All the code from this series of posts is available in the following repo: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/xaviermignot" rel="noopener noreferrer"&gt;
        xaviermignot
      &lt;/a&gt; / &lt;a href="https://github.com/xaviermignot/terraform-certificates" rel="noopener noreferrer"&gt;
        terraform-certificates
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A repository showing how to generate certificates using Terraform
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Certificate generation with Terraform for Azure App Service&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This repository contains sample code to generate TLS certificates using Terraform.&lt;br&gt;
It uses an Azure App Service as an example of a website to secure.&lt;/p&gt;
&lt;p&gt;The certificates are generated in 3 ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;By creating a self-signed certificate&lt;/li&gt;
&lt;li&gt;By requesting a certificate from Let's Encrypt&lt;/li&gt;
&lt;li&gt;By creating an Azure App Service managed certificate&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Obviously the third example can only work with Azure. The two others are written to work with Azure as well but can be adapted or used as an inspiration to work on other platforms.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting started&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Set the variables of the root module&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;If you want to run this against your Azure infrastructure you will need to provide values for the variables of the root module:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;dns_zone_name&lt;/code&gt; should contain the name of your DNS Zone managed in Azure&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;dns_zone_rg_name&lt;/code&gt; should contain the name of the resource group containing…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/xaviermignot/terraform-certificates" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;&lt;br&gt;
The &lt;code&gt;readme&lt;/code&gt; explains how to get started if you want to create the resources and generate the certificates in your own subscription.
&lt;h2&gt;
  
  
  Level 1: generating a self-signed certificate
&lt;/h2&gt;

&lt;p&gt;As a very first step we will generate a &lt;em&gt;self-signed&lt;/em&gt; certificate, something we will not do for production but can be handy for a quick test.&lt;br&gt;&lt;br&gt;
HashiCorp provides a &lt;a href="https://registry.terraform.io/providers/hashicorp/tls/latest" rel="noopener noreferrer"&gt;tls&lt;/a&gt; provider to generate the certificate using Terraform just like we will do using openssl in Linux or PowerShell in Windows.&lt;br&gt;&lt;br&gt;
Here is the HCL code to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a private key in PEM format&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"tls_private_key"&lt;/span&gt; &lt;span class="s2"&gt;"private_key"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;algorithm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"RSA"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Generates a TLS self-signed certificate using the private key&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"tls_self_signed_cert"&lt;/span&gt; &lt;span class="s2"&gt;"self_signed_cert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;key_algorithm&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tls_private_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;algorithm&lt;/span&gt;
  &lt;span class="nx"&gt;private_key_pem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tls_private_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key_pem&lt;/span&gt;

  &lt;span class="nx"&gt;validity_period_hours&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;

  &lt;span class="nx"&gt;subject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# The subject CN field here contains the hostname to secure&lt;/span&gt;
    &lt;span class="nx"&gt;common_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;common_name&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;allowed_uses&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"key_encipherment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"digital_signature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"server_auth"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's just two resources: a private key and a certificate generated using this private key.&lt;br&gt;&lt;br&gt;
We could stop here but in an Microsoft context we need to export the certificate in PFX format to use it in an Azure service such as App Service or Application Gateway.&lt;br&gt;&lt;br&gt;
This is done by combining the use of the HashiCorp &lt;a href="https://registry.terraform.io/providers/hashicorp/random/latest" rel="noopener noreferrer"&gt;random&lt;/a&gt; provider (to generate a password) and the third-party provider &lt;a href="https://registry.terraform.io/providers/chilicat/pkcs12/latest" rel="noopener noreferrer"&gt;pkcs12&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# To convert the PEM certificate in PFX we need a password&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_password"&lt;/span&gt; &lt;span class="s2"&gt;"self_signed_cert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;
  &lt;span class="nx"&gt;special&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# This resource converts the PEM certicate in PFX&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"pkcs12_from_pem"&lt;/span&gt; &lt;span class="s2"&gt;"self_signed_cert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cert_pem&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tls_self_signed_cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_signed_cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_pem&lt;/span&gt;
  &lt;span class="nx"&gt;private_key_pem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tls_private_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key_pem&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;random_password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_signed_cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Finally we push the PFX certificate in the Azure webspace&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_app_service_certificate"&lt;/span&gt; &lt;span class="s2"&gt;"self_signed_cert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"self-signed"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;

  &lt;span class="nx"&gt;pfx_blob&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pkcs12_from_pem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_signed_cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pkcs12_from_pem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_signed_cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it, the certificate is ready to be used as an App Service certificate as you can see in the full code &lt;a href="https://github.com/xaviermignot/terraform-certificates/blob/main/01_self_signed/main.tf" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;As you have probably guessed this certificate will not make the browser happy as our machine is not recognized as a trusted authority:&lt;br&gt;&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%2Ftvx2523upcgotfwda4y5.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%2Ftvx2523upcgotfwda4y5.png" alt="Browser error"&gt;&lt;/a&gt; &lt;em&gt;The hostname matches but the browser still displays an error as the authority is not trusted&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But at least the Azure portal is happier as it considers that the custom domain is now secured:&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%2Fb08hc9sce9v0275pjbma.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%2Fb08hc9sce9v0275pjbma.png" alt="Azure portal"&gt;&lt;/a&gt; &lt;em&gt;The Azure portal seems less picky than the browser...&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;That's it for this first step, which serves as an introduction to next posts. As I already mentioned the use of self-signed certificates is not recommended for production, and next you will see we can do much better than that.&lt;br&gt;&lt;br&gt;
If we take a look back to our initial diagram, we have made the following changes:&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%2Fdc4ogxt1ihpncn1qty1x.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%2Fdc4ogxt1ihpncn1qty1x.png" alt="Generating self-signed certs"&gt;&lt;/a&gt; &lt;em&gt;Diagram of the generation and deployment of the self-signed cert&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Stay tuned for the next post who will be about Let's Encrypt. If you already need it, you can go to my &lt;a href="https://github.com/xaviermignot/terraform-certificates" rel="noopener noreferrer"&gt;repo&lt;/a&gt; as the code is already there 🤓&lt;br&gt;&lt;br&gt;
Thanks for reading !&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>azure</category>
      <category>appservice</category>
    </item>
    <item>
      <title>The Book Lender API - Powered by Low-Code, deployed by code</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Tue, 08 Mar 2022 22:42:42 +0000</pubDate>
      <link>https://dev.to/xaviermignot/the-book-lender-api-powered-by-low-code-deployed-by-code-36bf</link>
      <guid>https://dev.to/xaviermignot/the-book-lender-api-powered-by-low-code-deployed-by-code-36bf</guid>
      <description>&lt;h3&gt;
  
  
  Overview of My Submission
&lt;/h3&gt;

&lt;p&gt;This project has kept me busy over the last few days, and is probably going to keep me busy for the next few weeks 😅&lt;/p&gt;

&lt;p&gt;The global idea is to help a small group of people to lend books to each other and to track who each book is lent to.&lt;br&gt;
The inspiration behind this comes from my wife who is running an association with four of her friends.&lt;br&gt;&lt;br&gt;
This association aims to help parents in our locality whose children have neurodevelopmental disorders, by creating social link, organizing events to gather the families, help them to get in touch with the right people.&lt;br&gt;&lt;br&gt;
If you understand French and want to know more about the association you can check their FB page &lt;a href="https://www.facebook.com/parentaisesolidaire/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So globally the association is ran by 5 persons, and has a few dozen of members. The people running the association have several books that they lend to the other members, and it's getting difficult to track where the books are.&lt;br&gt;&lt;br&gt;
They could have used a spreadsheet for this but my wife asked me if I could build something better 😊  &lt;/p&gt;

&lt;p&gt;Technically the app is composed of a frontend powered by VueJs 3 and Bootstrap 5, an API built with Azure Logic Apps exposed through Azure API Management and the data is stored in Cosmos Db. As I still have a ton of work to do on the frontend I have decided to scope my submission to the backend side of the project. &lt;br&gt;
I have probably over-estimated my frontend skills for this project, which are closed to zero as I'm a backend developer who is currently working in an infrastructure team. But I have stepped out of my comfort zone and learned a lot over the last few days, and will continue to do so. &lt;/p&gt;
&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;Low-Code Legends&lt;/p&gt;

&lt;p&gt;As I've written on the project's readme, I have hesitated a bit before choosing a category, I hope my submission is compliant with this one 🤞&lt;/p&gt;
&lt;h3&gt;
  
  
  Link to Code on GitHub
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/xaviermignot"&gt;
        xaviermignot
      &lt;/a&gt; / &lt;a href="https://github.com/xaviermignot/book-lender-app"&gt;
        book-lender-app
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Web App to help small groups of people to lend books to each other
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Book Lender App&lt;/h1&gt;
&lt;p&gt;This repository will contain the code of an application designed to help small groups of people to lend books to each other.&lt;br&gt;
The project is in its very early stage of development/design, this file will be updated throughout the process.&lt;/p&gt;
&lt;h2&gt;
The story of this project&lt;/h2&gt;
&lt;p&gt;There is a story behind each application, this one came from my wife who is running an association with a couple of her friends
The purpose of this association is to help parents in our locality whose children have (or might have) neurodevelopmental disorders, by creating social link between the families, helping parents to get in touch with the right professionals, organizing events and many other things.&lt;br&gt;
If you want to know more about the association you can check their &lt;a href="https://www.facebook.com/parentaisesolidaire/" rel="nofollow"&gt;Facebook page&lt;/a&gt;, just beware it's in French 🥐.&lt;/p&gt;
&lt;p&gt;The association is run by five people (let's call them 'the board…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/xaviermignot/book-lender-app"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;The app's design is centered around the following principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimized for use on a smartphone, with a few use cases: lend a book, list the books, and return a book&lt;/li&gt;
&lt;li&gt;Use the camera to scan the ISBN bar codes already present on the books (trying to avoid the use of the keyboard at most)&lt;/li&gt;
&lt;li&gt;From a ISBN code, the information of a book can be retrieved using an external source like Google Books API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This diagram shows the whole architecture of the project:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tQGXphLZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r37jwxzauhm1g6auwup3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tQGXphLZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r37jwxzauhm1g6auwup3.png" alt="Architecture diagram" width="880" height="623"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Introducing... Low-Code as Code
&lt;/h4&gt;

&lt;p&gt;As I have mentioned in previous posts I try to deploy the Azure resources of my projects using IaC, mostly Terraform, and for this one the Logic Apps was more challenging than usual.&lt;br&gt;
I have described in the project's &lt;a href="https://github.com/xaviermignot/book-lender-app#deploying-logic-apps-using-terraform"&gt;readme&lt;/a&gt; on how I have overcome theses challenges.  &lt;/p&gt;

&lt;p&gt;The Logic Apps themselves are not very complicated, here is a screenshot of the designer showing a snippet of the "Get book by ISBN" workflow when the data is either retrieved from Google Books API or from Cosmos Db:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i3hSs8ZT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ko0i5esvlqjcdn5ty1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3hSs8ZT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ko0i5esvlqjcdn5ty1f.png" alt="Logic Apps designer" width="880" height="454"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Exposing the Logic Apps using API Management
&lt;/h4&gt;

&lt;p&gt;Importing the Logic Apps in API Management turns a bunch of isolated Logic Apps into a single API definition:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HQNlMdqV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3cyt969361v43083w7a2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HQNlMdqV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3cyt969361v43083w7a2.png" alt="APIM" width="880" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So that the Logic Apps can be called as an API using tools like Postman or Rest-Client in VS Code:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x9BJpqQU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qx0uiiq6un1dgaa5xm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x9BJpqQU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qx0uiiq6un1dgaa5xm5.png" alt="Rest Client" width="880" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using API Management in the consumption tier brings the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Protect the API using policies, from CORS to rate limits&lt;/li&gt;
&lt;li&gt;Manage API keys for apps and other consumers of the API&lt;/li&gt;
&lt;li&gt;Adding a custom domain with a managed certificate (currently in preview)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  A quick view of the frontend
&lt;/h4&gt;

&lt;p&gt;Last but not the least I have uploaded this quick video to share my progress on the frontend side (be gentle):&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/SkUV__KrKTA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The scanning UI is brought by &lt;a href="https://github.com/mebjas/html5-qrcode"&gt;this awesome lib&lt;/a&gt;. Once the scan is complete, the API is called and the result is quickly displayed in the view. &lt;br&gt;
Of course this will be improved in the upcoming weeks, this project will live beyond the Azure Trial Hackathon, as it's a real world project and people are waiting for me to finish this 🤗&lt;/p&gt;

&lt;p&gt;Thanks for reading !&lt;/p&gt;

</description>
      <category>azuretrialhack</category>
      <category>azure</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Bug hunting story: Azure App Service, Easy Auth and cross domain POST queries</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Sun, 30 Jan 2022 18:01:32 +0000</pubDate>
      <link>https://dev.to/xaviermignot/bug-hunting-story-azure-app-service-easy-auth-and-cross-domain-post-queries-58a9</link>
      <guid>https://dev.to/xaviermignot/bug-hunting-story-azure-app-service-easy-auth-and-cross-domain-post-queries-58a9</guid>
      <description>&lt;p&gt;This is the kind of post that would have saved me some time a few days ago, so let's write it to help others, including future me.&lt;br&gt;&lt;br&gt;
So I was facing the following situation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A web application A is making POST requests to a web application B&lt;/li&gt;
&lt;li&gt;Everything worked fine until I have activated App Service built-in authentication on application B&lt;/li&gt;
&lt;li&gt;Then all POST requests from web app A to web app B ended up with 403 errors 😒&lt;/li&gt;
&lt;li&gt;Disabling authentication on web app B made the problem disappear&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few more info in this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web application B is running in a plain Azure App Service&lt;/li&gt;
&lt;li&gt;Authentication has been added using the built-in authentication of App Service, also called "Easy Auth". Read the doc &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/overview-authentication-authorization"&gt;here&lt;/a&gt; if you're not familiar with this feature&lt;/li&gt;
&lt;li&gt;This authentication is used to restrict the audience of web app B without touching its code. It's running on a QA environment whose access is limited to a few Azure AD accounts (of course it's based on Azure AD 😎)&lt;/li&gt;
&lt;li&gt;Web app A and web app B are not running on the same domain (this is some serious hint btw 😏)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reproducing the issue
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why not building a "quick" demo to demonstrate the issue ?
&lt;/h3&gt;

&lt;p&gt;I have prepared a small repository &lt;a href="https://github.com/xaviermignot/azure-easy-auth-cross-domain-post"&gt;here&lt;/a&gt; if you need to see the issue by yourself. It's composed of a Blazor app containing a form element to make POST requests to a .NET 6 API hosted in an Azure App Service.&lt;br&gt;&lt;br&gt;
As you have probably guessed, the Blazor app is web app A and the .NET 6 API is web app B.&lt;br&gt;&lt;br&gt;
The following diagram describes quickly the "architecture" of the thing:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NGKXrCMs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1tpf2t7v68nvr7379bqh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NGKXrCMs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1tpf2t7v68nvr7379bqh.png" alt="Diagram" width="880" height="1129"&gt;&lt;/a&gt; &lt;em&gt;Really, an architecture diagram even for such a simple thing ?&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;I have already deleted the Azure resources, so the URLs you will see in the screenshots will not work. If you want to recreate the demo you can follow the instructions in the repo's readme. Basically there is a Bash script that creates the Azure resources using Terraform, builds the code and deploys it (maybe I've pushed things a little bit too far for a "quick" demo...).  &lt;/p&gt;

&lt;h3&gt;
  
  
  Well, just a few screenshots will do the trick...
&lt;/h3&gt;

&lt;p&gt;A few screenshots should be enough to understand the issue, starting with the Blazor app:  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s6MwZfKt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vwo4g9e68vhrun6rk8z6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s6MwZfKt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vwo4g9e68vhrun6rk8z6.png" alt="Blazor App" width="880" height="245"&gt;&lt;/a&gt; &lt;em&gt;The Blazor app containing the form to make a POST request from a domain to another one&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;Clicking on the &lt;em&gt;Send request&lt;/em&gt; button will submit the form and make a POST request to the API stored in another domain. The first time we do this, we are redirected to &lt;em&gt;login.microsoftonline.com&lt;/em&gt; for authentication, and once authenticated to this:  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zJFyGJ-x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u4ke1kc2t25cfxp25mif.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zJFyGJ-x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u4ke1kc2t25cfxp25mif.png" alt="Ok request" width="880" height="227"&gt;&lt;/a&gt; &lt;em&gt;Once authenticated, we get a 200 response to a GET request&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Why is there a GET request and not a POST one ? This is because of the identity provider that redirects the user to the web page through a GET request, event if a POST was initially made.&lt;br&gt;&lt;br&gt;
If we get back to the Blazor app and click on the button again, a POST request is finally made and we reproduce the issue:  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rH0pR_pF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/25u734l4mzz7iqzvf1pd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rH0pR_pF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/25u734l4mzz7iqzvf1pd.png" alt="Error reproduced !" width="880" height="391"&gt;&lt;/a&gt; &lt;em&gt;When the user is authenticated, the POST requests ends up with a 403 response&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Behind the scene, App Service sets a &lt;code&gt;AppServiceAuthSession&lt;/code&gt; cookie in the browser, so that there is no need to be redirected to the identity provider each time a request is made.&lt;/p&gt;

&lt;p&gt;The following diagrams sums up what just happened in a few steps:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nOuO3zOL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uo78tbkrk9blqecbz6i1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nOuO3zOL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uo78tbkrk9blqecbz6i1.png" alt="Workflow" width="880" height="1136"&gt;&lt;/a&gt; &lt;em&gt;The sequence of the actions causing the issue&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the issue
&lt;/h2&gt;

&lt;p&gt;So how can we track this issue ? I'll try to be brief, but let me share a trick that I've discovered while trying to fix this. In fact your browser's DevTools (I'm using Edge Chromium but I guess Firefox and Chrome do the same thing) can help you to copy the request with its headers for use in another tool.&lt;br&gt;&lt;br&gt;
Do this go to the &lt;em&gt;Network&lt;/em&gt; blade of the DevTools, right-click on the failing request, select &lt;em&gt;Copy&lt;/em&gt;, and then the "entry" that you want:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GgmbcaSQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yspxfmwmf83zy770dc97.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GgmbcaSQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yspxfmwmf83zy770dc97.png" alt="Edge DevTools" width="880" height="590"&gt;&lt;/a&gt; &lt;em&gt;The browser devtools can help to copy the request for replaying it in another tool like Postman or RestClient&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;I personally use the &lt;em&gt;Copy request headers&lt;/em&gt; entry to replay the request in VS Code using the great &lt;a href="https://marketplace.visualstudio.com/items?itemName=humao.rest-client"&gt;REST Client&lt;/a&gt; extension, but you can use curl or PowerShell for instance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--emT66Hlr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4zh1b1ad2pme61ghw9if.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--emT66Hlr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4zh1b1ad2pme61ghw9if.png" alt="VS Code REST Client" width="880" height="505"&gt;&lt;/a&gt; &lt;em&gt;Replaying the failing request in an external tool can help you to understand what's happening&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What I like about REST Client is the ability to comment some of the headers in the left pane, replay the request and see what changed in the right pane.&lt;br&gt;&lt;br&gt;
Here is what I've tried in this case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changing the verb from POST to GET results in a 200 response but we already knew that&lt;/li&gt;
&lt;li&gt;Removing the &lt;code&gt;Cookie&lt;/code&gt; header results in a 302 response with the &lt;code&gt;Location&lt;/code&gt; header as &lt;em&gt;login.windows.net&lt;/em&gt;. This is logical as when the &lt;code&gt;AppServiceAuthSession&lt;/code&gt; cookie is not present, App Service redirects the user to the identity provider, Azure AD in this case.&lt;/li&gt;
&lt;li&gt;Removing or changing the &lt;code&gt;User-Agent&lt;/code&gt; header while keeping the &lt;code&gt;Cookie&lt;/code&gt; header results in a 200 response with &lt;em&gt;Successful POST request !!!&lt;/em&gt; in the body 🤯. This is quite interesting and explains the &lt;em&gt;real&lt;/em&gt; origin of our issue 🤔&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So why is the request rejected when the &lt;code&gt;User-Agent&lt;/code&gt; header says that it was initiated by an internet &lt;em&gt;browser&lt;/em&gt; ? Is there some mechanism that prevents the request from being sent from a domain to another one ?&lt;br&gt;&lt;br&gt;
As you might have guessed the culprit is the Cross-Origin Resource Sharing mechanism, aka &lt;em&gt;CORS&lt;/em&gt; ! I thought that CORS was used only for request fired from JavaScript, but in fact it's more complicated that that.&lt;br&gt;&lt;br&gt;
In our case the combination of cross-domain, POST request, &lt;code&gt;Cookie&lt;/code&gt; header with session data and &lt;code&gt;User-Agent&lt;/code&gt; telling that we are a browser "triggers" CORS and therefore blocks the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing the issue
&lt;/h2&gt;

&lt;p&gt;Well, once we know why the issue is occurring it's pretty straightforward to fix it. We just have to update the configuration of our App Service (aka our API) to add the domain of the Blazor app in the list of allowed origins.&lt;br&gt;&lt;br&gt;
For instance you can do this in the Azure portal:  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IZQx0oGh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/48tws6c194nic6bpt9a1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IZQx0oGh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/48tws6c194nic6bpt9a1.png" alt="CORS blade in the Azure portal" width="880" height="636"&gt;&lt;/a&gt; &lt;em&gt;The fix in the Azure portal&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;But this can also be done using &lt;em&gt;az cli&lt;/em&gt;, or any IaC tool you use to provision your resources.&lt;br&gt;&lt;br&gt;
Once it has been applied, wait for a few seconds, the change is not instant even if Azure says it has been updated.&lt;br&gt;&lt;br&gt;
Just be patient, and then the POST requests will get 200 responses as they were supposed to have !&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I have to admit I spent way more time on building this demo and writing this post than working on the issue itself. This was also an opportunity to try some cool stuff like Blazor, .NET minimal APIs and of course Bash scripting which is the coolest, right ?&lt;br&gt;&lt;br&gt;
Anyway it was interesting to learn or re-learn that CORS is not only for the calls made from the Javascript code. I hope this post will help some of my peers, as it might help me in the future.&lt;br&gt;&lt;br&gt;
If you want to dig more into what is CORS I can't recommend enough the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS"&gt;MDN&lt;/a&gt; documentation.&lt;br&gt;&lt;br&gt;
And overall never step back in front of a bug, even if it seems boring or impossible to solve, keep fixing things you might be about to learn something new.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>azuread</category>
      <category>appservice</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Use Terraform Cloud for your pet projects</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Sun, 12 Sep 2021 18:28:54 +0000</pubDate>
      <link>https://dev.to/xaviermignot/use-terraform-cloud-for-your-pet-projects-3dom</link>
      <guid>https://dev.to/xaviermignot/use-terraform-cloud-for-your-pet-projects-3dom</guid>
      <description>&lt;p&gt;Over the last few years I have been interested by IaC (Infrastructure as Code), and started recently to use Terraform in my professional life. To keep practicing my Terraform skills I also started to use it for POCs and personal projects instead of using the portal.&lt;br&gt;&lt;br&gt;
Even if I had to push myself at the beginning, I now have a routine to initialize and manage my Azure resources that I will share in this post. It relies on Terraform Cloud that you can use for free !&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Terraform and Terraform Cloud ?
&lt;/h2&gt;

&lt;p&gt;This post is not a deep dive into Terraform, we will stick to the basics. But if you are very new to Terraform, I encourage you to browse the product &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;website&lt;/a&gt; and follow a &lt;a href="https://learn.hashicorp.com/terraform" rel="noopener noreferrer"&gt;basic tutorial&lt;/a&gt; as it's the best way to know what the tool is about.&lt;br&gt;&lt;br&gt;
To better understand the differences between Terraform and Terraform Cloud, let's sum up the differences real quick:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform is the open-source CLI tool with the &lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt;, &lt;code&gt;destroy&lt;/code&gt; commands that can run anywhere (including on your machine or in Terraform Cloud)&lt;/li&gt;
&lt;li&gt;Terraform Cloud is a SaaS offering to manage Terraform runs and states remotely&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Is it free ?
&lt;/h3&gt;

&lt;p&gt;Terraform CLI is &lt;a href="https://github.com/hashicorp/terraform" rel="noopener noreferrer"&gt;open-source&lt;/a&gt; and free to use. Terraform Cloud is also free for teams up to 5 users, so it's perfect for pet projects.&lt;br&gt;&lt;br&gt;
Terraform Cloud has paid plans for larger organizations, and also a self-hosted version called Terraform Enterprise. That's not the topic of this post, but it's important to know how the company behind the tool makes money.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create your Terraform Cloud workspace
&lt;/h2&gt;

&lt;p&gt;Let's get finally to the point, shall we ? For this sample we will create an Azure Function App which will result in creating the following resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A resource group&lt;/li&gt;
&lt;li&gt;An App Service Plan&lt;/li&gt;
&lt;li&gt;A storage account&lt;/li&gt;
&lt;li&gt;A Function App&lt;/li&gt;
&lt;li&gt;An Application Insights account (optional but always handy to monitor the app's telemetry)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;To do this we will need the following prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Terraform Cloud account with an organization, to create one follow the guidelines from &lt;a href="https://learn.hashicorp.com/tutorials/terraform/cloud-sign-up?in=terraform/cloud-get-started#create-an-account" rel="noopener noreferrer"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An Azure subscription, a &lt;a href="https://azure.microsoft.com/en-us/free/" rel="noopener noreferrer"&gt;free&lt;/a&gt; one will be enough&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/cli/azure/" rel="noopener noreferrer"&gt;Azure CLI&lt;/a&gt; installed and connected to your subscription&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Create your workspace
&lt;/h3&gt;

&lt;p&gt;Once you have your &lt;em&gt;organization&lt;/em&gt; (which represents your &lt;em&gt;team&lt;/em&gt;) in Terraform Cloud, you will need a &lt;em&gt;workspace&lt;/em&gt; (which represents your &lt;em&gt;project&lt;/em&gt;).&lt;br&gt;&lt;br&gt;
Go to &lt;a href="https://app.terraform.io/" rel="noopener noreferrer"&gt;Terraform Cloud&lt;/a&gt;, select you organization, and click on the &lt;em&gt;"&lt;strong&gt;New workspace&lt;/strong&gt;"&lt;/em&gt; button.  &lt;/p&gt;

&lt;p&gt;On the next page you have to choose a workflow, choose &lt;em&gt;CLI-driven workflow&lt;/em&gt;:&lt;br&gt;&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%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-cloud%2F02-workspace-type.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%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-cloud%2F02-workspace-type.png" alt="CLI-driven workflow"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The CLI-driven workflow on the new workspace page&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Using this workflow allows you to run Terraform commands from your machine, but they will run remotely in Terraform Cloud. It's the best of two worlds approach as you get the simplicity of running the commands from your terminal of choice, and you get the security of having the state stored remotely, and the runs are accessible in Terraform Cloud.&lt;/p&gt;
&lt;h3&gt;
  
  
  Give Terraform Cloud access to your Azure subscription
&lt;/h3&gt;

&lt;p&gt;Next you need to connect your workspace with you Azure subscription. Start by creating a new service principal with this Azure CLI command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;az ad sp create-for-rbac &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;SERVICE PRINCIPAL NAME&amp;gt;'&lt;/span&gt; &lt;span class="nt"&gt;--role&lt;/span&gt; Owner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choosing a relevant name will be more helpful than the default &lt;code&gt;azure-cli-datetime&lt;/code&gt; name. Specifying the &lt;code&gt;Owner&lt;/code&gt; role is needed if you plan to assign a role to a resource, using a Managed Identity for instance.&lt;/p&gt;

&lt;p&gt;The command will output a JSON object:&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;"appId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;A NEW GUID&amp;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;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service-principal-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;"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;"http://service-principal-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;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;A SUPER SECRET STRING&amp;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;"tenant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;YOU TENANT ID&amp;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;Then back in Terraform Cloud you need to create &lt;em&gt;environment variables&lt;/em&gt; from the &lt;em&gt;Variables&lt;/em&gt; tab of your workspace page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ARM_CLIENT_ID&lt;/code&gt;: The Service Principal's id if from the previous command's output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ARM_CLIENT_SECRET&lt;/code&gt;: The Service Principal's secret also from the command output (you should tick the &lt;em&gt;Sensitive&lt;/em&gt;  checkbox for this one)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ARM_SUBSCRIPTION_ID&lt;/code&gt;: Your subscription id, you can get it with this command: &lt;code&gt;az account show --query id -o tsv&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ARM_TENANT_ID&lt;/code&gt;: Your tenant id, also from the command output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should get something like this:&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%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-cloud%2F03-environment-variables.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%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-cloud%2F03-environment-variables.png" alt="Environment variables"&gt;&lt;/a&gt;&lt;em&gt;These variables are then used by Terraform to get tokens for calling the Azure REST API.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Set a few variables for your project
&lt;/h3&gt;

&lt;p&gt;Still from the &lt;em&gt;Variables&lt;/em&gt; tab of your workspace page, you need to create the following &lt;em&gt;Terraform variables&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;project&lt;/code&gt; contains a string used in the names of the Azure resources. It has to be something that will make the names unique globally&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;location&lt;/code&gt; contains the name of the Azure region you want to use, in az cli style such as &lt;code&gt;eastus&lt;/code&gt;, &lt;code&gt;westus&lt;/code&gt;, &lt;code&gt;westeurope&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that these variables are not &lt;em&gt;environment&lt;/em&gt; variables, they are not used in the shell but in the Terraform code.&lt;/p&gt;

&lt;p&gt;Here is how I have set the variables in my workspace:&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%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-cloud%2F04-terraform-variables.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%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-cloud%2F04-terraform-variables.png" alt="Terraform variables"&gt;&lt;/a&gt;&lt;em&gt;My resources will be created in the France Central Azure region, and called afa-xmi-tf-sample, ai-xmi-tf-sample, etc.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Get the code &amp;amp; get ready to deploy
&lt;/h2&gt;

&lt;p&gt;I have made up a GitHub repo with the resources listed above, you can grab it &lt;a href="https://github.com/xaviermignot/terraform-sample" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
So you don't have to write the Terraform code for now but you can explore it and tweak it as you want.&lt;/p&gt;
&lt;h3&gt;
  
  
  Repository content
&lt;/h3&gt;

&lt;p&gt;For simplicity I have put all the files at the root of the repository. In a "full" project I would have several folders dedicated to the Terraform files, the source, the tests, the doc, etc.&lt;br&gt;&lt;br&gt;
But there is only Terraform files here so it's pretty simple, we can just note that we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;az-*.tf&lt;/code&gt; files containing the creation of the Azure resources&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tf-*.ft&lt;/code&gt; files containing Terraform configuration such as the variables declaration and the required providers&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Add the &lt;code&gt;tf-backend.tf&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;There is one file that you need to add to establish the link between the repo and your Terraform Cloud workspace. Just create the &lt;code&gt;tf-backend.tf&lt;/code&gt; file with the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"remote"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;organization&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;YOUR ORGANIZATION NAME&amp;gt;"&lt;/span&gt;

    &lt;span class="nx"&gt;workspaces&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;YOUR WORKSPACE NAME&amp;gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file is git-ignored as it contains the name of the Terraform Cloud organization and workspace. Even if those are not secrets, I consider as good practice not to commit environment-related values like this, especially in public repositories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's deploy the resources !
&lt;/h3&gt;

&lt;p&gt;We are finally getting to the point where we are going to deploy some stuff in Azure. Let's jump in your favorite terminal in the root of the repo if you are not already there, and fire a few commands.&lt;br&gt;&lt;br&gt;
First and just once run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;terraform login&lt;/code&gt; to authenticate to your Terraform Cloud workspace using your browser&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform init&lt;/code&gt; to install the required providers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then you are ready to use the most common Terraform CLI commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;terraform plan&lt;/code&gt; to create a &lt;em&gt;plan&lt;/em&gt; to let Terraform determine the changes to make on your infrastructure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform apply&lt;/code&gt; to &lt;em&gt;apply&lt;/em&gt; the plan and finally make the changes (which will result in the creation of all resources when you run this for the first time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that feel free to tweak the Terraform files, experiment, and run the &lt;code&gt;plan&lt;/code&gt; and &lt;code&gt;apply&lt;/code&gt; commands again. Use the Azure portal to see what you have deployed:&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%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-cloud%2F05-azure-portal.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%2Fblog.xmi.fr%2Fassets%2Fimg%2Fterraform-cloud%2F05-azure-portal.png" alt="Azure portal"&gt;&lt;/a&gt;&lt;em&gt;You should get something similar to this&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Once you are done, you can delete all the resources with the &lt;code&gt;terraform destroy&lt;/code&gt; command.  &lt;/p&gt;

&lt;p&gt;What I like about this approach is that even if you can see the output of the commands in you terminal, everything is running in the cloud, and the state of the infrastructure is securely stored in the cloud as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up &amp;amp; next steps
&lt;/h2&gt;

&lt;p&gt;We have seen in this post how to deploy resources using the CLI approach of Terraform Cloud. While it might seem overkill to do this for personal projects or POCs, I think it's a good compromise as it combines the good practice to have the state securely stored in the cloud with the comfort of running the commands from my terminal (and not to have to push a change to trigger a CI/CD pipeline).&lt;br&gt;&lt;br&gt;
When I finish late my work on a project, I also like to run &lt;code&gt;terraform destroy&lt;/code&gt;, approve the changes and close my laptop right away, as everything is running somewhere else... in the cloud !  &lt;/p&gt;

&lt;p&gt;This post describes a first step in my Terraform journey, and a big one as I needed quit some time to make up this post 😇&lt;br&gt;&lt;br&gt;
Then I might go further with the following next steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automate things&lt;/strong&gt;: I could write a script to create the workspace, the service principal and configure everything in a single step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manage environments&lt;/strong&gt;: while it's not necessary in the early stages of pet projects, being able to manage several environments (DEV, PROD, ...) could be interesting to do, and useful for my professional life&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve security&lt;/strong&gt;: creating a service principal with the Owner role does not following the principle of least privilege... there must be a way to ensure that its access are scoped to the dedicated resource group&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Bicep&lt;/strong&gt;: not related to Terraform at all but I know I will try this new way of deploying stuff in Azure some day 😅&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it for this post, thanks for reading, don't hesitate to reach out, and happy &lt;em&gt;terraforming&lt;/em&gt; 🤓&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>azure</category>
    </item>
    <item>
      <title>Microsoft Visio 101: Additional tips</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Sun, 04 Apr 2021 21:42:41 +0000</pubDate>
      <link>https://dev.to/xaviermignot/microsoft-visio-101-additional-tips-2i05</link>
      <guid>https://dev.to/xaviermignot/microsoft-visio-101-additional-tips-2i05</guid>
      <description>&lt;p&gt;This post closes the &lt;em&gt;Microsoft Visio 101&lt;/em&gt; series after an &lt;a href="https://dev.to/xaviermignot/microsoft-visio-101-the-basic-tools-2k6b"&gt;introduction to the tools&lt;/a&gt;, and a focus on &lt;a href="https://dev.to/xaviermignot/microsoft-visio-101-align-your-shapes-like-a-boss-5agd"&gt;positioning and aligning shapes&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
For this one I don't have a specific topic, but I have remaining tips to share 😊&lt;/p&gt;

&lt;h2&gt;
  
  
  Find your own style
&lt;/h2&gt;

&lt;p&gt;As a backend developer/architect, making diagrams is one of my few opportunities to make something nice-looking in my professional life. So over the years I have tried to improve my diagrams to give them a &lt;em&gt;personal&lt;/em&gt; style with very simple tweaks, starting with connectors.  &lt;/p&gt;

&lt;p&gt;Let's start with this few shapes linked together:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uW-TGJ2V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/msirifwfvihhxaghainx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uW-TGJ2V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/msirifwfvihhxaghainx.png" alt="Linked shapes"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
The alignment and positioning looks good, but the connectors with the default settings give a raw, unfinished feeling. The contextual menu can open the &lt;em&gt;Format Shape&lt;/em&gt; panel and set the connector style between right-angle, straight and curved:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VLNhvrwy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ncmsdyvswnxr07lxmuar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VLNhvrwy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ncmsdyvswnxr07lxmuar.png" alt="Contextual menu"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
The curved style is great for softening the angles, but it's not easy to work with so I stick with the right-angle style all the time. It's much easier to get something symmetric, and to soften the angles I increase the &lt;em&gt;rounding size&lt;/em&gt; in the &lt;em&gt;format shape&lt;/em&gt; panel to 3mm. I also increase the &lt;em&gt;width&lt;/em&gt; to 1pt and choose an &lt;em&gt;end arrow type&lt;/em&gt; (I like the 02).&lt;br&gt;&lt;br&gt;
These 3 little changes are my go-to style for connectors, and make the diagram look much better:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TJfd4N5_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t8sgd3t2ax276yg4gfey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TJfd4N5_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t8sgd3t2ax276yg4gfey.png" alt="Styled connectors"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
I haven't found a way to set this as the &lt;em&gt;default&lt;/em&gt; style for connectors, so I set it once on a connector and use the &lt;code&gt;Ctrl+Shift+P&lt;/code&gt; shortcut to apply it quickly on other connectors.  &lt;/p&gt;

&lt;p&gt;Another thing I like to do is putting shapes in the same "box" when they're related in some way. For instance Azure resources in the same &lt;em&gt;resource group&lt;/em&gt; or &lt;em&gt;domain&lt;/em&gt;, or to break down the underlying features of a resource, like this IoT Hub for example:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LJ-Nv-iO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/355n1iv1i3f4iw6qntys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LJ-Nv-iO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/355n1iv1i3f4iw6qntys.png" alt="IoT Hub raw"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
By setting the &lt;em&gt;transparency&lt;/em&gt; to 50%, increasing the &lt;em&gt;width&lt;/em&gt; to 1.5pt, choosing a &lt;em&gt;dash type&lt;/em&gt; and setting the &lt;em&gt;rounding size&lt;/em&gt; to 3mm, all from the format shape panel, it's more stylish:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aPilRQ3u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eskcrma6hlp31c48ah2y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aPilRQ3u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eskcrma6hlp31c48ah2y.png" alt="IoT Hub styled"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;These are just a few changes I do all the time to improve my diagrams, I encourage you to experiment with the format shape panel, find your style and give your diagram a personal touch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the text background feature
&lt;/h2&gt;

&lt;p&gt;Adding text to a connector can be made in different ways, depending on the length of the text and the connector. When the text is shorter than the connector, I like to place the text on top of the connector. But when there is too little room the text would hide the connector so I prefer to split it and keep its background transparent.&lt;br&gt;&lt;br&gt;
You can see the difference here:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--glrIBYQi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yrzdax0f99qu4aiw72h3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--glrIBYQi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yrzdax0f99qu4aiw72h3.png" alt="Text background sample"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
This is done by selecting the connector, and setting the text background from either &lt;em&gt;None&lt;/em&gt; or &lt;em&gt;Solid color&lt;/em&gt; to white. Open the &lt;em&gt;Paragraph&lt;/em&gt; dialog, &lt;em&gt;Text block&lt;/em&gt; pane, and finally &lt;em&gt;Text background&lt;/em&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t5_oVZhi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6jaiuwc1d1xfon6spnx2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t5_oVZhi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6jaiuwc1d1xfon6spnx2.png" alt="Text background option"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Very simple tip but very handy as well, so worth sharing at the end I think.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sync you favorite shapes using git
&lt;/h2&gt;

&lt;p&gt;This last one is a trick I have been using for many years to synchronize my Visio shapes from various sources using git. I have made a &lt;a href="https://dev.to/xaviermignot/manage-your-visio-shapes-with-git-2h0i"&gt;dedicated post&lt;/a&gt; a few weeks ago so you can check it out.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;This is the end of &lt;em&gt;Microsoft 101&lt;/em&gt; series, at least for now, I might edit this last post if I have more ideas. I hope these posts will help you to improve you diagrams, and that you will enjoy doing diagrams as much as I do.&lt;br&gt;&lt;br&gt;
Happy drawing ! 🤓&lt;/p&gt;

</description>
      <category>visio</category>
    </item>
    <item>
      <title>Microsoft Visio 101: Align your shapes like a boss</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Sun, 28 Mar 2021 21:55:59 +0000</pubDate>
      <link>https://dev.to/xaviermignot/microsoft-visio-101-align-your-shapes-like-a-boss-5agd</link>
      <guid>https://dev.to/xaviermignot/microsoft-visio-101-align-your-shapes-like-a-boss-5agd</guid>
      <description>&lt;p&gt;A I really enjoy making architecture diagrams using Visio, I want to share in a few posts some tips to help you do the same.&lt;br&gt;&lt;br&gt;
This post is the second of this series, the &lt;a href="https://dev.to/xaviermignot/microsoft-visio-101-the-basic-tools-2k6b"&gt;previous one&lt;/a&gt; introduced the basic tools of Visio to manipulate the shapes. &lt;br&gt;
This one is about other tools that will help you to better position your shapes, and have nice perfect straight lines between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I never use PowerPoint for architecture diagrams
&lt;/h2&gt;

&lt;p&gt;As an introduction to shape alignment, here is one thing that bugs me in PowerPoint that make unusable to me for architecture diagrams. In the GIF below I simply select one shape then another, and use the &lt;em&gt;Align middle&lt;/em&gt; feature to align them on the same horizontal line:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lNQDq_rG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/alkkmiuwystvrvnkevzu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lNQDq_rG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/alkkmiuwystvrvnkevzu.gif" alt="Align middle in PowerPoint" width="880" height="560"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Align middle in PowerPoint style: both shapes moving towards each other&lt;/em&gt;&lt;br&gt;&lt;br&gt;
The behavior I expect when doing this is to use the first selected shape as a &lt;em&gt;reference&lt;/em&gt;, so that only the second selected shape will move to align with the first one. And you see what PowerPoint does ? It creates a new &lt;em&gt;reference&lt;/em&gt; by merging the position of both shapes 😱  &lt;/p&gt;

&lt;p&gt;On the other side Visio keeps the position of the first selected shape:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JxWoRNCx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hwju080azefuxfyr5h63.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JxWoRNCx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hwju080azefuxfyr5h63.gif" alt="Align middle in Visio" width="880" height="520"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Align middle in Visio style: shapes 2 moves towards shape 1&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Which seems like a detail but it makes me happy, as it' much easier to control the position of the shapes like this, especially when working with more than 2 shapes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the Align tool
&lt;/h2&gt;

&lt;p&gt;Already introduced in the previous section, the align does simply what it says: select several shapes, and use the align tool to left, right, center or middle align them.&lt;br&gt;&lt;br&gt;
Just remember that the first selected shape will not move and be used as a &lt;em&gt;reference&lt;/em&gt;, so that the following shapes will align by moving towards the first one. Oh and by the way, it's located in the &lt;em&gt;Arrange&lt;/em&gt; section of the &lt;em&gt;Home&lt;/em&gt; ribbon:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QGLuh40J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/00f19w9vlgb9f4ixg8oj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QGLuh40J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/00f19w9vlgb9f4ixg8oj.png" alt="Align tool" width="434" height="550"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
I almost use only the tool for center and middle align, in the example below I start to arrange these messy shapes by using center align twice and middle align once:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cuWJ0X6a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbp1vwj1m4valtz7wwby.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cuWJ0X6a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbp1vwj1m4valtz7wwby.gif" alt="Diagram align" width="880" height="568"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Align tool in action&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Using the align tool is a first step toward harmony in architecture diagrams, there is another tool that can help to organize your shapes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the Position tool
&lt;/h2&gt;

&lt;p&gt;The position tool is the one to use when you need to nicely, evenly position shapes in a diagram. The position tool is located right next to the align tool in the ribbon:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L_akjtOE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fuibvqhz8yyq893y233r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L_akjtOE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fuibvqhz8yyq893y233r.png" alt="Position tool" width="468" height="756"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
I use mostly the &lt;em&gt;distribute horizontally&lt;/em&gt; and &lt;em&gt;distribute vertically&lt;/em&gt;, when I have at least 3 shapes to evenly place. Note that with the position tool the shapes at the ends will remain still, only the shapes between them will move.&lt;br&gt;&lt;br&gt;
Let's get back to the previous diagram to see it in action:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R7gydf7z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bi1d4pe10sz3o3zsiavz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R7gydf7z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bi1d4pe10sz3o3zsiavz.gif" alt="Diagram position" width="880" height="548"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Position tool in action&lt;/em&gt;&lt;br&gt;&lt;br&gt;
So far we've seen that using the align and position tools together help to control the way we organize shapes in a diagram. But it's mostly useful when used jointly with connections between these shapes, and I have a last tip for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't be afraid of the ShapeSheet
&lt;/h2&gt;

&lt;p&gt;As I have mentioned in my previous &lt;a href="https://dev.to/xaviermignot/microsoft-visio-101-the-basic-tools-2k6b"&gt;post&lt;/a&gt;, Visio comes really handy when connecting shapes together, using the connection and the connection point tool.&lt;br&gt;&lt;br&gt;
However I encourage you to go a step further by using the &lt;a href="https://docs.microsoft.com/en-us/office/client-developer/visio/about-the-shapesheet-spreadsheet"&gt;ShapeSheet&lt;/a&gt; to manage connection points. The ShapeSheet is a spreadsheet associated to each shape that can be used to control the shapes properties. Think of it as the F12 key that brings the developers tools of your browser but for Visio.&lt;br&gt;&lt;br&gt;
Beside you have to activate the Visio &lt;a href="https://docs.microsoft.com/en-us/office/vba/visio/how-to/run-visio-in-developer-mode"&gt;developer mode&lt;/a&gt; to access it but don't freak out, we will only use it for one simple thing.&lt;br&gt;&lt;br&gt;
Once a shape has at least one connection point, I right click on it, I use the &lt;em&gt;Show ShapeSheet&lt;/em&gt; menu and organize the &lt;em&gt;Connection Points&lt;/em&gt; section to look like this:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cM4UBmXb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aonmucrxx9bl6hutvgt5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cM4UBmXb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aonmucrxx9bl6hutvgt5.png" alt="ShapeSheet" width="880" height="612"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
In this section I can add, edit and remove the shapes connection points and set their coordinates as I want using simple formulas using the height and width of the shape. And you can see on the shape in the upper part of the screenshot that the connection point are perfectly placed, way better than if I had placed them "manually".&lt;br&gt;&lt;br&gt;
Of course depending on the use case I can add more connection points, if I have several connections to place on the same side or to start a connection below the shape's text.  &lt;/p&gt;

&lt;p&gt;But what's really interesting is when I do this for all the shapes on my diagrams, I can then use the align and position tools to place my shapes, and the connections between them will always looks perfectly straight, avoiding those pesky offset pixels that ruin the harmony of a diagram.&lt;br&gt;&lt;br&gt;
See how it looks after the addition of the connections on the previous diagram:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bppBGTV---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ufvm8eekxxehdykmoa2d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bppBGTV---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ufvm8eekxxehdykmoa2d.png" alt="Diagram with connection" width="880" height="548"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Shapes aligned, position and nicely connected 👌&lt;/em&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;That's it for this post, we have seen that using simple shape positioning tools with a more advanced feature like the ShapeSheet make our diagrams start to look really nice. I have a few more tips to share in a last post of this series, I hope these posts associated with some practice will help you to create nice looking diagrams.&lt;br&gt;&lt;br&gt;
Note that the diagram I have use in the examples is issued from &lt;a href="https://dev.to/xaviermignot/azure-iot-hub-routing-device-twin-changes-deep-dive-52p8"&gt;this post&lt;/a&gt; if you want to check it out 😉&lt;/p&gt;

</description>
      <category>visio</category>
    </item>
    <item>
      <title>Microsoft Visio 101: The basic tools</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Sun, 21 Mar 2021 21:42:20 +0000</pubDate>
      <link>https://dev.to/xaviermignot/microsoft-visio-101-the-basic-tools-2k6b</link>
      <guid>https://dev.to/xaviermignot/microsoft-visio-101-the-basic-tools-2k6b</guid>
      <description>&lt;p&gt;A I really enjoy making architecture diagrams using Visio, I want to share in a few posts some tips to help you do the same.&lt;br&gt;&lt;br&gt;
This one is about the basic tools, Visio is a complex software so it's important to know basic stuff and not feel overwhelmed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the Visio "tools"
&lt;/h2&gt;

&lt;p&gt;To get started you should get comfortable with the &lt;em&gt;tools&lt;/em&gt; of Visio, which are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The pointer tool, to select and drag shapes&lt;/li&gt;
&lt;li&gt;The text block tool, to move the text associated to shapes&lt;/li&gt;
&lt;li&gt;The connector tool, to draw lines between shapes&lt;/li&gt;
&lt;li&gt;The connection point tool, to edit the connection points of a shape
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the tools are located in the &lt;code&gt;Home&lt;/code&gt; tab of the ribbon, in the &lt;code&gt;Tools&lt;/code&gt; toolbar:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DBnwpi0s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mahgxn5vun59cnxdaiv5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DBnwpi0s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mahgxn5vun59cnxdaiv5.png" alt="Tools toolbar"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
It's important to know their respective roles, and how to switch between them, it's quite simple you will see 🤗  &lt;/p&gt;

&lt;h3&gt;
  
  
  The pointer tool
&lt;/h3&gt;

&lt;p&gt;The pointer tool is the most basic, selected by default. You can use it to select, drag, copy shapes for instance.&lt;br&gt;&lt;br&gt;
Let's start by creating a rectangle in a blank diagram by clicking on the white rectangle in the toolbar:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W9ZSBIYf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i6sfl4mvdtiso03i2qny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W9ZSBIYf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i6sfl4mvdtiso03i2qny.png" alt="Create rectangle"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(This is another tool I didn't mentioned, you can use to draw basic shapes it's pretty straightforward 😉)&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Now you're in the &lt;em&gt;rectangle creation mode&lt;/em&gt;, so any click &amp;amp; drag in the diagram zone will draw a rectangle. If you want to stop drawing them and start moving them for instance, select the pointer tool again using the &lt;code&gt;Ctrl+1&lt;/code&gt; shortcut (or clicking on the pointer in the toolbar but memorizing the shortcut is worth it).&lt;br&gt;&lt;br&gt;
Then you'll be able to move around those rectangles, resize them, delete, any basic action you can expect from a diagram drawing tool.&lt;br&gt;&lt;br&gt;
That's all the pointer tool is about: getting back to the most basic mode of the tool, using the shortcut to memorize: &lt;code&gt;Ctrl+1&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Let's keep a single rectangle before moving to the second tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  The text block tool
&lt;/h3&gt;

&lt;p&gt;In Visio you can easily add some text to any shape: simply select the shape, start typing and that's it. You can hit F2 before typing but it's not mandatory.&lt;br&gt;&lt;br&gt;
So there is no need to manually associated a text zone to a shape, it's already within each shape.&lt;br&gt;&lt;br&gt;
You can use the text block tool to move or resize these text zones, without changing the shape itself. Select a shape, hit &lt;code&gt;Ctrl+Shift+4&lt;/code&gt; and arrange the text as you want.&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uV7J0gQp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/om7g4fgx89i38wspdkxm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uV7J0gQp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/om7g4fgx89i38wspdkxm.png" alt="Text with shape"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;h3&gt;
  
  
  The connector tool
&lt;/h3&gt;

&lt;p&gt;Connecting shapes is where Visio begins to shine in my opinion. The first tool for managing connection is the connector tool which creates connections between shapes.&lt;br&gt;&lt;br&gt;
To use it we are going to use more complex shapes than rectangles. Let's select the first shape available in the &lt;em&gt;Shapes&lt;/em&gt; panel, the &lt;em&gt;Main topic&lt;/em&gt; shape in the &lt;em&gt;Brainstorming Shapes&lt;/em&gt; stencil, and create two shapes:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hY68K_VR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3axu1zo8rnr7v3m02f7a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hY68K_VR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3axu1zo8rnr7v3m02f7a.png" alt="Main topic shape"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
These shapes have predefined &lt;em&gt;connection points&lt;/em&gt;, so if you hit &lt;code&gt;Ctrl+3&lt;/code&gt; to switch to the connector tool, you can easily draw a connection between the two shapes.&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---2l-TAMl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4jtf1m8dw6ctc2yjn4td.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---2l-TAMl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4jtf1m8dw6ctc2yjn4td.png" alt="Connect the left shape"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Draw a connection from one point in the green square...&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gny_nopk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/94kd9layto4ejtj9tmux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gny_nopk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/94kd9layto4ejtj9tmux.png" alt="To the right shape"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;...to another connection point&lt;/em&gt;&lt;br&gt;&lt;br&gt;
As the connection points anchors the connection to the shapes, you can move the shape around and the connection will follow naturally.&lt;/p&gt;

&lt;h3&gt;
  
  
  The connection point tool
&lt;/h3&gt;

&lt;p&gt;As all shapes do not come with predefined connection points, or sometime you need to add some, that's where the connection point comes in handy. &lt;br&gt;
Select a shape using the pointer tool, hit &lt;code&gt;Ctrl+Shift+1&lt;/code&gt; to switch to the connection point tool, and you can perform tasks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding a new connection point using &lt;code&gt;Ctrl+click&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Dragging an existing connection point&lt;/li&gt;
&lt;li&gt;Delete a connection point by clicking on it and hit &lt;code&gt;Delete&lt;/code&gt;
The connection point tool combined with the connector tool let you do anything you want with connection between shapes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;That's it for this post, we have covered the most basic features of Visio. Next time we will see how to use the align and position features to perfectly place shapes in a diagram.&lt;br&gt;&lt;br&gt;
Meanwhile, take some time to memorize the keyboard shortcuts and the role of each tool:  &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Shortcut&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pointer tool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Select, move &amp;amp; resize shapes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text block tool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+Shift+4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Arrange text linked to shapes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connector tool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Draw connections between shapes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection point tool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+Shift+1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Edit shapes connection points&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>visio</category>
    </item>
    <item>
      <title>Preparation guide for Azure certifications</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Sun, 14 Mar 2021 22:00:22 +0000</pubDate>
      <link>https://dev.to/xaviermignot/preparation-guide-for-azure-certifications-1clk</link>
      <guid>https://dev.to/xaviermignot/preparation-guide-for-azure-certifications-1clk</guid>
      <description>&lt;p&gt;As I have passed several Azure certifications, over the years I have established a routine for my studies before an exam. I will share this in this post alongside all the tips I can think of.&lt;/p&gt;

&lt;h2&gt;
  
  
  List the exam objectives
&lt;/h2&gt;

&lt;p&gt;It might sound obvious but the first thing to look at is the content of the exam. As exam content evolve, but not at the same pace as Azure services, it's not that obvious to known exactly what to expect.  &lt;/p&gt;

&lt;h3&gt;
  
  
  The exam page: your best source of true
&lt;/h3&gt;

&lt;p&gt;You can ask people who have sat the exam a few month or longer ago, but the way to have the most up-to-date objective list is to simply get it from the exam page on the MS Learn website. Search for your exam from &lt;a href="https://docs.microsoft.com/en-us/learn/certifications/browse/"&gt;here&lt;/a&gt;, and from the exam page click on "Download exam skills outline".  &lt;/p&gt;

&lt;p&gt;Let's take an example with the Azure Developer exam, &lt;a href="https://docs.microsoft.com/en-us/learn/certifications/exams/az-204"&gt;this&lt;/a&gt; is the page we are looking for:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ajFwRwJY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0dw3asd2angb3wt88fyo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ajFwRwJY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0dw3asd2angb3wt88fyo.png" alt="Exam page"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Then scroll down to the "Skills measured" section:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a-F3_jxW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qxtn5smz7iicjn3y4k0o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a-F3_jxW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qxtn5smz7iicjn3y4k0o.png" alt="Skill measured section"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;This section contains valuable information. First if there is a schedule change on the exam content, it will be noticed here. Then there is the sections list of the exam, with a percentage range aside. It's not clear if this percentage if related to the number of questions or how each section weights in the score calculation, but you should pay attention to this percentage and use it when you prioritize your studies.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Making a to-do list
&lt;/h3&gt;

&lt;p&gt;What I usually do from here is download the &lt;em&gt;skills outline&lt;/em&gt; document, and build a to-do list from it like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a section with each section of the exam, sorted by percentage (higher to lower)&lt;/li&gt;
&lt;li&gt;Within each section, write the list of the objectives&lt;/li&gt;
&lt;li&gt;Finally add a checkbox for each bullet point under each objective
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use OneNote for this, create a notebook for each exam, here is an example for the AZ-202 exam:&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IvCxElVm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9bd1aphnzv4ex5eb9vsw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IvCxElVm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9bd1aphnzv4ex5eb9vsw.png" alt="OneNote"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Then the idea is to tick all the boxes beside the things I already have done or know enough, and leave it unchecked for the things I need to work on. &lt;br&gt;
Throughout my studying I check the boxes to track my progress. I don't necessary need to tick everything, I might skip some items if the section percentage is low or if it's something I barely know.&lt;/p&gt;

&lt;h3&gt;
  
  
  Be aware of changes !
&lt;/h3&gt;

&lt;p&gt;Last tip on the exam objective, remember that the exam page and the skills outline document are you only source or up-to-date information. If you plan to take your time for studying, get back to it from time to time to see if something has changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Microsoft Learn
&lt;/h2&gt;

&lt;p&gt;Listing the things to do is first step, a quite boring one, and is less important since the release of the Microsoft Learn &lt;a href="https://docs.microsoft.com/en-us/learn/"&gt;website&lt;/a&gt;. You'll find resources online to find out &lt;a href="https://docs.microsoft.com/en-us/learn/support/faq?pivots=general"&gt;what Microsoft Learn is&lt;/a&gt;, but shortly it's a platform with tons of tutorials on how to use Microsoft products, including Azure services.  &lt;/p&gt;

&lt;p&gt;It's free, content is growing, and it's kinda fun as there are &lt;em&gt;gamification&lt;/em&gt; features with levels, xp, and achievements.&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XOhcpl5b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cz32g9h84g24hhznmfdv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XOhcpl5b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cz32g9h84g24hhznmfdv.png" alt="Ms Learn XP"&gt;&lt;/a&gt;&lt;em&gt;See how much xp I have 😎&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I strongly encourage you to use it as one of your primary sources of learning, with the following additional tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an account to track your progress and gain xp, it's more rewarding/addictive&lt;/li&gt;
&lt;li&gt;Use bookmarks and collections to save up things you wanna learn&lt;/li&gt;
&lt;li&gt;Under each exam page, there is a list of &lt;em&gt;learning paths&lt;/em&gt; to follow to prepare for the exam. Take as much as you can !&lt;/li&gt;
&lt;li&gt;There are some questions after each module, which are relatively easy. Don't be too confident if you always find the good answers, as the questions is the exams are usually harder 😉&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  MeasureUp practice tests
&lt;/h2&gt;

&lt;p&gt;At some point you will consider getting a practice test from MeasureUp, as those are official practice tests. I have already used them a few times, here are their advantages in my opinion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The interface is similar, but not exactly like the real exam UI. So if it's your first exam, it helps to get familiar with the exam experience and the type of questions.&lt;/li&gt;
&lt;li&gt;Each answer comes with an explanation, and resources to know more about it. So if you're wrong, you can learn why and improve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NgC7ATH1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzqlu7ohu9q6s1j882hx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NgC7ATH1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzqlu7ohu9q6s1j882hx.png" alt="Sample question"&gt;&lt;/a&gt;&lt;em&gt;A sample question from a practice test...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UOr8sxYf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uho5rka9a2khalbrmli4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UOr8sxYf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uho5rka9a2khalbrmli4.png" alt="Answer explanation"&gt;&lt;/a&gt;&lt;em&gt;...and the answer with explanations&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Additionally here are some tips I can provide on the practice tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not spend too much time working on them. It's tempting to dedicate almost all your studying time to it, as you want to improve your score, but be careful. I have already done this too much, and at the end I knew almost all questions by heart, which was worthless. Remember you're preparing to get certified on a piece of tech, not on a set of questions !&lt;/li&gt;
&lt;li&gt;Opinions will vary on this, but personally I have almost never met a question in a real exam that was in a practice test. This a good and a bad point, the practice test does not provide the questions in advance, it prepares you to think in the way the real exam is made.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lastly if you search for practice tests on the web, outside of the official ones you will find tons of websites that guarantee 100% success with &lt;em&gt;real&lt;/em&gt; questions. Do not trust them, these are bullshit, barely legal sources I think, and the worst way to prepare for an exam: don't try to fill your brain with pre-answered questions, try to learn something that will help you in your everyday job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other sources of information
&lt;/h2&gt;

&lt;p&gt;As there a also great resources outside of the official ones, let me share here some of my favorites.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blog posts
&lt;/h3&gt;

&lt;p&gt;Remember the list of objective I mention at the beginning ? Well I'm not the first with this idea, even better there are some great people who do this and share resources associated to each objective:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gregor Suttie, aka Azure Greg, is an MVP who shares a huge blog post each time he passes an exam, which happens a lot. Checks out &lt;a href="https://gregorsuttie.com/"&gt;his blog&lt;/a&gt; and look for &lt;em&gt;study guides&lt;/em&gt; posts&lt;/li&gt;
&lt;li&gt;Stanislas Quastana is a cloud solution architect for partners at Microsoft France. I had the chance to meet him a few times for training on certifications. His preparation guides on &lt;a href="https://stanislas.io/"&gt;his blog&lt;/a&gt; are worth checking out, and packed with super great PowerPoint decks. Some of his posts are in French 🥖🐓&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reddit
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.reddit.com/r/AZURE"&gt;Azure subreddit&lt;/a&gt; is a place where people can discuss about Azure stuff. There are often discussions on certifications, whether it's a preparation guide or folks sharing their experience on exams.&lt;br&gt;&lt;br&gt;
The search feature is handy, search for your exam code, limit the results to the Azure subreddit, and you might find some valuable informations (example with the &lt;a href="https://www.reddit.com/r/AZURE/search?q=az-204&amp;amp;restrict_sr=1"&gt;AZ-204&lt;/a&gt; exam).  &lt;/p&gt;

&lt;p&gt;Careful though, in the discussions there are some people (maybe bots) who advise to go to some of the shitty websites I mentioned earlier, with &lt;em&gt;dumps&lt;/em&gt; and &lt;em&gt;100% guaranteed success&lt;/em&gt;. Once again avoid them like the plague, you'll have more chance to infect your computer with a virus than learning something worth it using them.&lt;/p&gt;

&lt;p&gt;A little reminder, when you're looking at a blog post or a discussion on Reddit, always check the date of the post. Exam content always change, so the fresher the post is, the better. If you have any doubt, check on the exam page !&lt;/p&gt;

&lt;h3&gt;
  
  
  Pluralsight (and other learning platforms)
&lt;/h3&gt;

&lt;p&gt;I don't use it very much as I prefer reading text over watching videos, but Pluralsight has some great resources to prepare for an exam. I heard that Pete Gallagher provides great &lt;a href="https://app.pluralsight.com/profile/author/peter-gallagher"&gt;courses&lt;/a&gt; for those who are preparing for the AZ-220 exam on Azure IoT for instance.&lt;br&gt;&lt;br&gt;
One feature I particularly like on Pluralsight is the &lt;a href="https://app.pluralsight.com/skilliq"&gt;Skill IQ&lt;/a&gt; assessments. For each topic of an exam, it provides a set of questions, with limited time to answer, and at the end you get a score and how you rank among other "students". Similarly to the practice tests, it's not the best feature for learning, but it helps to gain confidence.&lt;br&gt;&lt;br&gt;
There are other learning platforms like Udemy or Linux Academy that I have slightly used, you can go check them as another source of learning content. &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I'm done writing about Azure certifications for now, I hope this post and the previous one will help if you want to get certified. If that's the case, great, remember that you're doing it for yourself and to learn something new.&lt;br&gt;&lt;br&gt;
And if you don't want to, it's fine, don't feel forced to get certified, you can have a happy professional life without that.&lt;br&gt;&lt;br&gt;
Once again, happy learning ! 🤓&lt;/p&gt;

</description>
      <category>azure</category>
      <category>certifications</category>
    </item>
  </channel>
</rss>
