<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https:/blog.johnalfaro.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https:/blog.johnalfaro.com/" rel="alternate" type="text/html" /><updated>2025-07-05T22:13:13+10:00</updated><id>https:/blog.johnalfaro.com/feed.xml</id><title type="html">The Cloud Journey</title><subtitle>a bit of everything around cloud specially around Azure.</subtitle><author><name>John Alfaro</name></author><entry><title type="html">Terraforming Cost Anomaly Detection on AWS and Azure</title><link href="https:/blog.johnalfaro.com/blog/terraform-cost-anomaly-detection" rel="alternate" type="text/html" title="Terraforming Cost Anomaly Detection on AWS and Azure" /><published>2025-07-05T00:00:00+10:00</published><updated>2025-07-05T00:00:00+10:00</updated><id>https:/blog.johnalfaro.com/blog/terraform-cost-anomaly-detection</id><content type="html" xml:base="https:/blog.johnalfaro.com/blog/terraform-cost-anomaly-detection"><![CDATA[<blockquote class="notice--info">
  <p><strong>TL;DR:</strong><br />
Learn how to set up cost anomaly detection on AWS and Azure using Terraform, compare their features, and see which is best for your FinOps needs.</p>
</blockquote>

<hr />

<h2 id="introduction-">Introduction 🚀</h2>

<p>Cost overruns can derail even the most well-planned cloud strategies. Fortunately, both AWS and Azure offer native capabilities for detecting cost anomalies. In this blog, I’ll show how each provider approaches this challenge, highlight their pros and cons, and share how I implemented Terraform configurations to create anomaly detection alerts for both.</p>

<hr />

<h2 id="-aws-cost-anomaly-detection">🟧 AWS Cost Anomaly Detection</h2>

<table>
  <thead>
    <tr>
      <th><span style="color:green;">✔️ Pros</span></th>
      <th><span style="color:red;">❌ Cons</span></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ML-based detection adapts over time</td>
      <td>Requires initial setup of monitors and subscriptions</td>
    </tr>
    <tr>
      <td>High granularity: monitor services, accounts, regions, or tags</td>
      <td>No unit cost attribution by customer or project</td>
    </tr>
    <tr>
      <td>Integrates with SNS (Slack, email, Lambda)</td>
      <td>Still reactive – after cost spikes occur</td>
    </tr>
  </tbody>
</table>

<blockquote class="notice--info">
  <p><strong>💡 Pro Tip:</strong><br />
AWS lets you set anomaly alert thresholds using absolute dollar values or percentage increases, making it easy to tailor alerts for your business needs.</p>
</blockquote>

<h3 id="how-it-works">How it works</h3>

<p>AWS uses machine learning models to monitor usage patterns across services, accounts, and tags. It generates alerts via Amazon SNS when anomalies are detected.</p>

<h3 id="terraform-example">Terraform Example</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">resource</span> <span class="s2">"aws_ce_anomaly_monitor"</span> <span class="s2">"example"</span> <span class="p">{</span>
  <span class="nx">name</span>              <span class="p">=</span> <span class="s2">"FinOps Anomaly"</span>
  <span class="nx">monitor_type</span>      <span class="p">=</span> <span class="s2">"DIMENSIONAL"</span>
  <span class="nx">monitor_dimension</span> <span class="p">=</span> <span class="s2">"SERVICE"</span>
  <span class="nx">tags</span>              <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">tags</span>
<span class="p">}</span>

<span class="nx">resource</span> <span class="s2">"aws_ce_anomaly_subscription"</span> <span class="s2">"example"</span> <span class="p">{</span>
  <span class="nx">name</span>      <span class="p">=</span> <span class="s2">"FinOps Anomaly Alert"</span>
  <span class="nx">frequency</span> <span class="p">=</span> <span class="s2">"DAILY"</span>
  <span class="nx">monitor_arn_list</span> <span class="p">=</span> <span class="p">[</span>
    <span class="nx">aws_ce_anomaly_monitor</span><span class="err">.</span><span class="nx">example</span><span class="err">.</span><span class="nx">arn</span>
  <span class="p">]</span>
  <span class="nx">subscriber</span> <span class="p">{</span>
    <span class="nx">type</span>    <span class="p">=</span> <span class="s2">"EMAIL"</span>
    <span class="nx">address</span> <span class="p">=</span> <span class="s2">"me@johnalfaro.com"</span>
  <span class="p">}</span>
  <span class="nx">threshold_expression</span> <span class="p">{</span>
    <span class="nx">dimension</span> <span class="p">{</span>
      <span class="nx">key</span>           <span class="p">=</span> <span class="s2">"ANOMALY_TOTAL_IMPACT_ABSOLUTE"</span>
      <span class="nx">match_options</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"GREATER_THAN_OR_EQUAL"</span><span class="p">]</span>
      <span class="nx">values</span>        <span class="p">=</span> <span class="p">[</span><span class="s2">"100"</span><span class="p">]</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="nx">tags</span> <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">tags</span>
<span class="p">}</span>
</code></pre></div></div>

<p>After a couple of days, you will start receiving emails with alerts like this one:</p>

<p><img src="/assets/images/Blog/2025-07-05/awsano.jpg" alt="AWS Anomaly Alert" class="align-center" /></p>

<hr />

<h2 id="-azure-cost-anomaly-detection">🟦 Azure Cost Anomaly Detection</h2>

<table>
  <thead>
    <tr>
      <th><span style="color:green;">✔️ Pros</span></th>
      <th><span style="color:red;">❌ Cons</span></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Native integration with Cost Management</td>
      <td>Max 5 alert rules per subscription</td>
    </tr>
    <tr>
      <td>Drill-down into resource groups or subscriptions</td>
      <td>36-hour delay post-usage before detection runs</td>
    </tr>
    <tr>
      <td>Uses WaveNet models for forecasting</td>
      <td>Less flexible than AWS</td>
    </tr>
  </tbody>
</table>

<blockquote class="notice--info">
  <p><strong>ℹ️ Note:</strong><br />
Azure anomaly detection is easy to set up and integrates with Cost Management, but alerting and automation options are limited.</p>
</blockquote>

<h3 id="how-it-works-1">How it works</h3>

<p>Azure Cost Anomaly Detection is built on Microsoft’s WaveNet forecasting models and analyzes daily cost usage trends against historical data (up to 60 days). It looks for unexpected spikes in costs for a subscription or a resource group by comparing forecasted vs actual usage.</p>

<h3 id="terraform-example-1">Terraform Example</h3>

<p>More info on setting up Azure Cost Anomaly Detection can be found in the <a href="https://registry.terraform.io/providers/hashicorp/azurerm/4.35.0/docs/resources/cost_anomaly_alert">Azure Terraform Provider documentation</a>.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">resource</span> <span class="s2">"azurerm_cost_anomaly_alert"</span> <span class="s2">"sub"</span> <span class="p">{</span>
  <span class="nx">name</span>               <span class="p">=</span> <span class="s2">"finopsanomaly"</span>
  <span class="nx">display_name</span>       <span class="p">=</span> <span class="s2">"FinOps Anomaly Alert"</span>
  <span class="nx">subscription_id</span>    <span class="p">=</span> <span class="s2">"/subscriptions/${data.azurerm_subscription.me.subscription_id}"</span>
  <span class="nx">email_subject</span>      <span class="p">=</span> <span class="s2">"FinOps Alert - ${data.azurerm_subscription.me.display_name}"</span>
  <span class="nx">email_addresses</span>    <span class="p">=</span> <span class="p">[</span><span class="s2">"me@johnalfaro.com"</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>After a couple of days, you will start receiving emails with alerts like this one:</p>

<p><img src="/assets/images/Blog/2025-07-05/azano.jpg" alt="Azure Anomaly Alert" class="align-center" /></p>

<hr />

<h2 id="-comparison-table">📊 Comparison Table</h2>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>AWS</th>
      <th>Azure</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ML Detection</td>
      <td>✅ Yes</td>
      <td>✅ Yes (WaveNet)</td>
    </tr>
    <tr>
      <td>Detection Frequency</td>
      <td>~3x per day</td>
      <td>Daily (36h delay)</td>
    </tr>
    <tr>
      <td>Alert Flexibility</td>
      <td>SNS, email, webhook, Lambda</td>
      <td>Email only, 5-rule limit</td>
    </tr>
    <tr>
      <td>Granularity</td>
      <td>Service, region, tags, etc.</td>
      <td>Subscription, resource group</td>
    </tr>
    <tr>
      <td>Thresholds</td>
      <td>Absolute or percentage</td>
      <td>Percentage only</td>
    </tr>
    <tr>
      <td>Integration</td>
      <td>SNS, Lambda, EventBridge</td>
      <td>Cost Management only</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="-what-else-can-you-do-with-the-alerts">🚦 What Else Can You Do With the Alerts?</h2>

<h3 id="-aws">🟧 AWS</h3>

<p>AWS Cost Anomaly Detection integrates natively with Amazon SNS, unlocking powerful automation and downstream workflows:</p>

<ul>
  <li><strong>Trigger Lambda functions:</strong> Automate cost mitigation (e.g., resource tagging, notification routing, stopping instances).</li>
  <li><strong>Integrate with chat tools:</strong> Use AWS Chatbot or webhooks to notify Slack, Microsoft Teams, or Amazon Chime.</li>
  <li><strong>Store alerts in S3:</strong> Archive structured alerts for audit and trend analysis.</li>
  <li><strong>Route via EventBridge:</strong> Enable advanced rule-based routing and cross-service workflows.</li>
</ul>

<p>This allows for fully automated cost guardrails beyond basic email notifications.</p>

<h3 id="-azure">🟦 Azure</h3>

<p>Azure’s anomaly alerting is more limited, but still offers options:</p>

<ul>
  <li><strong>Email notifications:</strong> Default mechanism for anomaly alerts.</li>
  <li><strong>API polling workaround:</strong> Use Azure Functions or Logic Apps to query anomalies via the CostManagement/anomalyDetectionResults API and act on them.</li>
</ul>

<blockquote class="notice--info">
  <p><strong>ℹ️ At this time, Azure does not support direct integration of anomaly detection with Action Groups.</strong></p>
</blockquote>

<hr />

<h2 id="summary-">Summary 🎯</h2>

<p>AWS offers more flexibility and precision in anomaly detection. Its integration with other AWS services like Lambda, Step Functions, EventBridge, and Chatbot enables more automation and cost guardrails beyond simple notifications, making it a better option for enterprise environments.</p>

<p>Azure, while easy to set up and integrated with Cost Management, is falling behind in practical usability. The lack of direct support for Action Groups, limited alerting mechanisms, and a tendency to produce unnecessary alerts makes the feature less actionable and noisy. As it stands, Azure’s anomaly detection alerts are not yet ideal for enterprise-grade operations in my opinion.</p>

<p>I hope Microsoft invests in improving this feature to make it more reliable and enterprise-ready—definitely something every FinOps team wants to have, but it’s not quite there yet.</p>

<p>Let me know what you think—are you using either provider’s detection service? Or maybe both? 😄</p>

<p><strong>☕ Liked this content? You can sponsor my next deep dive below!</strong></p>

<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="" /></a></p>]]></content><author><name>John Alfaro</name></author><category term="Blog" /><category term="AWS" /><category term="Azure" /><category term="Terraform" /><category term="Cost Management" /><category term="FinOps" /><summary type="html"><![CDATA[In this post, we explore how AWS and Azure enable cost anomaly detection, compare their capabilities, and demonstrate how Terraform can be used to configure alerting for each platform.]]></summary></entry><entry><title type="html">Terraforming the Azure Open AI service Deployment via AzAPI and AzureRM providers for the Enterprise… and chat GPT model</title><link href="https:/blog.johnalfaro.com/blog/AOI" rel="alternate" type="text/html" title="Terraforming the Azure Open AI service Deployment via AzAPI and AzureRM providers for the Enterprise… and chat GPT model" /><published>2023-04-12T00:00:00+10:00</published><updated>2023-04-12T00:00:00+10:00</updated><id>https:/blog.johnalfaro.com/blog/AOI</id><content type="html" xml:base="https:/blog.johnalfaro.com/blog/AOI"><![CDATA[<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>

<p>Hey there! Have you heard of Azure Open AI? It’s an awesome platform developed by Microsoft that helps businesses of all sizes and industries leverage the power of artificial intelligence (AI) to achieve their digital transformation goals. With Azure Open AI, organizations can automate tasks, gain insights, and make better decisions faster than ever before.</p>

<p>One of the coolest things about Azure Open AI is that it’s designed to be super flexible and scalable, making it a popular choice for enterprises looking to adapt quickly to changing market conditions. Plus, it includes advanced security features like encryption and access controls to help protect customer data and systems from cyber threats.</p>

<p>Here are some of the ways that security is managed for the service to be operational.</p>

<ol>
  <li>Encryption: Azure Open AI uses encryption to protect data both in transit and at rest. This helps prevent unauthorized access to sensitive data.</li>
  <li>Access Controls: Azure Open AI includes a range of access controls to help prevent unauthorized access to customer data and systems. This includes role-based access controls, multi-factor authentication, and network security groups.</li>
  <li>Threat Detection: Azure Open AI includes advanced threat detection capabilities that can help organizations detect and respond to potential security threats. This includes real-time threat monitoring, security alerts, and automated threat response.</li>
  <li>Data Protection: Azure Open AI includes a range of data protection features, including backup and disaster recovery, to help ensure that customer data is protected in the event of a data loss or system failure.</li>
</ol>

<p>The diagram below depicts the architecture to be deployed for the Azure Open AI Account. By the way, I chose East US as Chat GPT is currently in preview in that region.</p>

<p><img src="/assets/images/Blog/2023-04-14/aoi.jpg" alt="Azure OpenAI Architecture" class="align-center" /></p>

<p>One of the key features is that Azure Open AI supports Private Endpoints, which let you restrict access to your Azure Open AI services to only those resources within your virtual network that require access. This keeps your services safe and secure, while still allowing authorized resources to access what they need.</p>

<p>For that reason, I will be focusing on the deployment of Azure Open AI using the <a href="https://registry.terraform.io/providers/hashicorp/azurerm">Terraform AzureRM provider</a> and <a href="https://registry.terraform.io/providers/Azure/azapi">Terraform AzAPI provider</a> to enable and manage this feature. I did not know this was already possible when I started exploring, so I have included both deployment methods. However, I did notice a significant difference in deployment time: the AzAPI provider was much faster than the AzureRM provider.</p>

<hr />

<h3 id="azure-open-ai-onboarding-️️">Azure Open AI Onboarding 🕵️‍♀️</h3>

<p>To get started with Azure Open AI, follow these steps:</p>

<ol>
  <li>Have your Azure subscription ID ready to get onboarded.</li>
  <li>Fill out the <a href="https://customervoice.microsoft.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR7en2Ais5pxKtso_Pz4b1_xUOFA5Qk1UWDRBMjg0WFhPMkIzTzhKQ1dWNyQlQCN0PWcu">Request Access Form</a>. It takes a couple of days to get a response.</li>
  <li><strong>If you are an Enterprise customer and want to ensure your data is not processed by Microsoft for abuse monitoring, you can opt out by filling out</strong> <a href="https://aka.ms/oai/modifiedaccess">this form</a>.</li>
  <li>I already have my Terraform Cloud Org set up for my deployment. 😎</li>
  <li>Deploy the Azure Open AI service via Terraform.</li>
  <li>Deploy the model to be used via Terraform.</li>
</ol>

<blockquote class="notice--info">
  <p><strong>ℹ️ Data Privacy:</strong><br />
For important information regarding data privacy, please refer to the <a href="https://learn.microsoft.com/en-us/legal/cognitive-services/openai/data-privacy">Azure OpenAI Data Privacy documentation</a>.</p>
</blockquote>

<hr />

<h3 id="terraform-configuration-using-azapi-provider-">Terraform Configuration Using AzAPI Provider 👨‍💻</h3>

<p>When I first looked into deploying Azure Open AI with Terraform, I noticed the AzureRM provider did not have an OpenAI resource. So, my first attempt was with the AzAPI provider. The code I used is below. (I have more AzAPI examples in earlier articles if you’re interested.)</p>

<p><strong>Azure Open AI Account Example:</strong></p>

<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Resource Group for Cognitive Services</span>
<span class="k">resource</span> <span class="s2">"azurerm_resource_group"</span> <span class="s2">"openai"</span> <span class="p">{</span>
  <span class="nx">name</span>     <span class="p">=</span> <span class="s2">"cerocool-ai-rg"</span>
  <span class="nx">location</span> <span class="p">=</span> <span class="s2">"eastus"</span>
<span class="p">}</span>

<span class="c1"># Azure Open AI Account</span>
<span class="k">resource</span> <span class="s2">"azapi_resource"</span> <span class="s2">"openai_account"</span> <span class="p">{</span>
  <span class="nx">type</span>      <span class="p">=</span> <span class="s2">"Microsoft.CognitiveServices/accounts@2022-12-01"</span>
  <span class="nx">name</span>      <span class="p">=</span> <span class="s2">"cerocoolai-azapi"</span>
  <span class="nx">parent_id</span> <span class="p">=</span> <span class="k">resource</span><span class="p">.</span><span class="nx">azurerm_resource_group</span><span class="p">.</span><span class="nx">openai</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">location</span>  <span class="p">=</span> <span class="s2">"eastus"</span>
  <span class="nx">body</span> <span class="p">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">sku</span> <span class="p">=</span> <span class="p">{</span>
      <span class="nx">name</span> <span class="p">=</span> <span class="s2">"S0"</span>
    <span class="p">}</span>
    <span class="nx">kind</span> <span class="p">=</span> <span class="s2">"OpenAI"</span>
    <span class="nx">properties</span> <span class="p">=</span> <span class="p">{</span>
      <span class="nx">publicNetworkAccess</span> <span class="p">=</span> <span class="s2">"Disabled"</span>
      <span class="nx">customSubDomainName</span> <span class="p">=</span> <span class="s2">"ceroazapi"</span>
    <span class="p">}</span>
  <span class="p">})</span>
<span class="p">}</span>

<span class="c1"># Azure Open AI Model - Chat GPT</span>
<span class="k">resource</span> <span class="s2">"azapi_resource"</span> <span class="s2">"chatgpt_model_azapi"</span> <span class="p">{</span>
  <span class="nx">type</span>      <span class="p">=</span> <span class="s2">"Microsoft.CognitiveServices/accounts/deployments@2022-12-01"</span>
  <span class="nx">name</span>      <span class="p">=</span> <span class="s2">"cero_chatgpt_model"</span>
  <span class="nx">parent_id</span> <span class="p">=</span> <span class="k">resource</span><span class="p">.</span><span class="nx">azapi_resource</span><span class="p">.</span><span class="nx">openai_account</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">body</span> <span class="p">=</span> <span class="nx">jsonencode</span><span class="p">({</span>
    <span class="nx">properties</span> <span class="p">=</span> <span class="p">{</span>
      <span class="nx">model</span> <span class="p">=</span> <span class="p">{</span>
        <span class="nx">format</span>  <span class="p">=</span> <span class="s2">"OpenAI"</span><span class="p">,</span>
        <span class="nx">name</span>    <span class="p">=</span> <span class="s2">"gpt-35-turbo"</span>
        <span class="nx">version</span> <span class="p">=</span> <span class="s2">"0301"</span>
      <span class="p">}</span>
      <span class="nx">scaleSettings</span> <span class="p">=</span> <span class="p">{</span>
        <span class="nx">scaleType</span> <span class="p">=</span> <span class="s2">"Standard"</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="terraform-configuration-using-azurerm-provider-">Terraform Configuration using AzureRM Provider 👷</h3>
<p>After digging a bit more I realise you can use Cognitive Services resource to deploy the resource😎.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">###</span><span class="w"> </span><span class="err">Resource</span><span class="w"> </span><span class="err">Group</span><span class="w"> </span><span class="err">for</span><span class="w"> </span><span class="err">Cognitive</span><span class="w"> </span><span class="err">Services</span><span class="w">
</span><span class="err">resource</span><span class="w"> </span><span class="s2">"azurerm_resource_group"</span><span class="w"> </span><span class="s2">"openai"</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="err">name</span><span class="w">     </span><span class="err">=</span><span class="w"> </span><span class="s2">"cerocool-ai-rg"</span><span class="w">
  </span><span class="err">location</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="s2">"eastus"</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="err">###</span><span class="w"> </span><span class="err">Azure</span><span class="w"> </span><span class="err">Open</span><span class="w"> </span><span class="err">AI</span><span class="w"> </span><span class="err">Account</span><span class="w">
</span><span class="err">resource</span><span class="w"> </span><span class="s2">"azurerm_cognitive_account"</span><span class="w"> </span><span class="s2">"openai"</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="err">name</span><span class="w">                          </span><span class="err">=</span><span class="w"> </span><span class="s2">"cerocoolai-azurerm"</span><span class="w">
  </span><span class="err">location</span><span class="w">                      </span><span class="err">=</span><span class="w"> </span><span class="s2">"eastus"</span><span class="w">
  </span><span class="err">resource_group_name</span><span class="w">           </span><span class="err">=</span><span class="w"> </span><span class="err">azurerm_resource_group.openai.name</span><span class="w">
  </span><span class="err">kind</span><span class="w">                          </span><span class="err">=</span><span class="w"> </span><span class="s2">"OpenAI"</span><span class="w">
  </span><span class="err">custom_subdomain_name</span><span class="w">         </span><span class="err">=</span><span class="w"> </span><span class="s2">"ceroaiazurerm"</span><span class="w">
  </span><span class="err">sku_name</span><span class="w">                      </span><span class="err">=</span><span class="w"> </span><span class="s2">"S0"</span><span class="w">
  </span><span class="err">public_network_access_enabled</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">false</span><span class="w">
  </span><span class="err">identity</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="err">type</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="s2">"SystemAssigned"</span><span class="w">
  </span><span class="p">}</span><span class="w">

  </span><span class="err">tags</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="err">Acceptance</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="s2">"Test"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="err">###</span><span class="w"> </span><span class="err">Azure</span><span class="w"> </span><span class="err">Open</span><span class="w"> </span><span class="err">AI</span><span class="w"> </span><span class="err">Model</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="err">Chat</span><span class="w"> </span><span class="err">GPT</span><span class="w">
</span><span class="err">resource</span><span class="w"> </span><span class="s2">"azurerm_cognitive_deployment"</span><span class="w"> </span><span class="s2">"chatgpt_model_azurerm"</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="err">name</span><span class="w">                 </span><span class="err">=</span><span class="w"> </span><span class="s2">"cero_chatgpt_model"</span><span class="w">
  </span><span class="err">cognitive_account_id</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">resource.azurerm_cognitive_account.openai.id</span><span class="w">
  </span><span class="err">model</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="err">format</span><span class="w">  </span><span class="err">=</span><span class="w"> </span><span class="s2">"OpenAI"</span><span class="w">
    </span><span class="err">name</span><span class="w">    </span><span class="err">=</span><span class="w"> </span><span class="s2">"gpt-35-turbo"</span><span class="w">
    </span><span class="err">version</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="s2">"0301"</span><span class="w">
  </span><span class="p">}</span><span class="w">

  </span><span class="err">scale</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="err">type</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="s2">"Standard"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>The resources created will look like below. However, what it really took my attention was the time of the deployment using AzAPI vs AzureRM provider. In this instance, the service took <code class="language-plaintext highlighter-rouge">16 mins</code> to be deployed via AzAPI and <code class="language-plaintext highlighter-rouge">23 mins</code> via AzureRM 🤔. Definitely, something I want to understand more as 7 mins difference seems quite a bit.
<img src="/assets\images\Blog\2023-04-14\deploy.png" alt="image-center" class="align-center" /></p>

<h4 id="private-endpoint-for-open-ai-resource-">Private Endpoint for Open AI Resource 📝</h4>
<p>To create the private endpoint for the resource you can use the snippet below. You will of course require having the <code class="language-plaintext highlighter-rouge">openai.azure.com</code> private DNS zone created for OpenAI. The service actually will let you know as soon as you remove the public access on the Open AI Studio.
<img src="/assets\images\Blog\2023-04-14\pe.png" alt="image-center" class="align-center" /></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">resource</span><span class="w"> </span><span class="s2">"azurerm_private_endpoint"</span><span class="w"> </span><span class="s2">"name"</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="err">name</span><span class="w">                </span><span class="err">=</span><span class="w"> </span><span class="s2">"openai-pe"</span><span class="w">
  </span><span class="err">location</span><span class="w">            </span><span class="err">=</span><span class="w"> </span><span class="s2">"westeurope"</span><span class="w">
  </span><span class="err">resource_group_name</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">azurerm_resource_group.openai.name</span><span class="w">
  </span><span class="err">subnet_id</span><span class="w">           </span><span class="err">=</span><span class="w"> </span><span class="err">data.azurerm_subnet.privatelink.id</span><span class="w">

  </span><span class="err">private_service_connection</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="err">name</span><span class="w">                           </span><span class="err">=</span><span class="w"> </span><span class="s2">"privateendpoint-1"</span><span class="w">
    </span><span class="err">private_connection_resource_id</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">azurerm_cognitive_account.openai.id</span><span class="w">
    </span><span class="err">is_manual_connection</span><span class="w">           </span><span class="err">=</span><span class="w"> </span><span class="kc">false</span><span class="w">
    </span><span class="err">subresource_names</span><span class="w">              </span><span class="err">=</span><span class="w"> </span><span class="p">[</span><span class="s2">"account"</span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="err">resource</span><span class="w"> </span><span class="s2">"azurerm_private_dns_a_record"</span><span class="w"> </span><span class="s2">"openai"</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="err">name</span><span class="w">                </span><span class="err">=</span><span class="w"> </span><span class="s2">"cerocoolai-azurerm"</span><span class="w">
  </span><span class="err">zone_name</span><span class="w">           </span><span class="err">=</span><span class="w"> </span><span class="s2">"privatelink.openai.azure.com"</span><span class="w">
  </span><span class="err">resource_group_name</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">azurerm_resource_group.openai.name</span><span class="w">
  </span><span class="err">ttl</span><span class="w">                 </span><span class="err">=</span><span class="w"> </span><span class="mi">300</span><span class="w">
  </span><span class="err">records</span><span class="w">             </span><span class="err">=</span><span class="w"> </span><span class="p">[</span><span class="err">azurerm_private_endpoint.openai.private_service_connection</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="err">.private_ip_address</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="summary-">Summary 📻</h3>
<p>In this blog post, we explored the Azure Open AI deployment process using terraform and highlighted some of the powerful capabilities that enterprises can leverage, such as Private Endpoint for enhanced security. We also discussed our surprise at the slow deployment times using AzureRM compared to AzAPI, though it’s worth noting that your experience may vary.
<strong>What’s next?</strong> 
Last but not least if you want to be part of the Azure OpenAI GPT-4 Public Preview Waitlist please check <a href="https://customervoice.microsoft.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR7en2Ais5pxKtso_Pz4b1_xURjE4QlhVUERGQ1NXOTlNT0w1NldTWjJCMSQlQCN0PWcu">here</a>.
<img src="/assets\images\Blog\2023-04-14\gp4.png" alt="image-center" class="align-center" /></p>

<p>I do hope this helps someone and that you find it informative,, so please let me know your constructive feedback as it’s always important🕵️‍♂️,, That’s it for now,,, Hasta la vista🐱‍🏍!!!</p>

<p><strong>🚴‍♂️ If you enjoyed this blog, you can empower me with some caffeine to continue working in new content 🚴‍♂️.</strong></p>

<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>]]></content><author><name>John Alfaro</name></author><category term="Blog" /><category term="Azure" /><category term="Terraform" /><category term="Azure Open AI" /><category term="TFC" /><summary type="html"><![CDATA[This article is demonstrating how to deploy Azure Open AI using the AzureRM and the AzAPI Terraform providers via Terraform Cloud]]></summary></entry><entry><title type="html">Managing Azure NetApp Files Volume Backup feature with Terraform AzAPI Provider</title><link href="https:/blog.johnalfaro.com/blog/ANF_Backup" rel="alternate" type="text/html" title="Managing Azure NetApp Files Volume Backup feature with Terraform AzAPI Provider" /><published>2022-08-24T00:00:00+10:00</published><updated>2022-08-24T00:00:00+10:00</updated><id>https:/blog.johnalfaro.com/blog/ANF_Backup</id><content type="html" xml:base="https:/blog.johnalfaro.com/blog/ANF_Backup"><![CDATA[<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>

<p>Azure NetApp Files data protection is extending to not just snapshots but it will be able to create volume backups based on volume snapshots, so I decided to test this preview feature to understand how the backup capability will enhance the data protection posture for ANF. Currently, Backups are a preview feature and it is not enabled yet on the <a href="https://registry.terraform.io/providers/hashicorp/azurerm">Terraform AzureRM provider</a>. For that reason, I decided to use the <a href="https://registry.terraform.io/providers/Azure/azapi">Terraform AzAPI provider</a> to enable and manage this feature.</p>

<p>Azure NetApp Files backup provides fully managed backup solution for long-term recovery, archive, and compliance.</p>

<ul>
  <li>Backups created by the service are stored in Azure Storage independent of volume snapshots. IT can be Zone-Redundant Storage (ZRS) where Availability Zones are available or Local Redundant Storage (LRS) where there are no Availability Zones support.</li>
  <li>Backups taken by the service can be restored to new ANF volume within the region.</li>
  <li>Azure NetApp Files backup supports both policy-based (scheduled) backups and manual (on-demand) backups. I will be focusing on policy-based as we all like consistency.(●’◡’●)</li>
</ul>

<p>For more information regarding this capability go to <a href="https://docs.microsoft.com/en-us/azure/azure-netapp-files/backup-introduction">ANF Backup documentation</a>.</p>

<p class="notice--info">The diagram below depicts the architecture to be deployed for Azure NetApp Files Account.</p>

<p><img src="/assets\images\Blog\2022-08-24\anfbackup.png" alt="image-center" class="align-center" /></p>

<h3 id="azure-netapp-files-backup-preview-enablement-️️">Azure NetApp Files Backup Preview enablement 🕵️‍♀️</h3>
<p>To enable the preview feature for Azure NetApp Files, you need to enable the preview feature. However, this feature needs to be requested via <a href="https://forms.office.com/pages/responsepage.aspx?id=v4j5cvGGr0GRqy180BHbR2Qj2eZL0mZPv1iKUrDGvc9UMkI3NUIxVkVEVkdJMko3WllQMVRNMTdEWSQlQCN0PWcu">Public Preview request form</a>. When the feature is enabled this will appear as registered.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-AzProviderFeature</span><span class="w"> </span><span class="nt">-ProviderNamespace</span><span class="w"> </span><span class="s2">"Microsoft.NetApp"</span><span class="w"> </span><span class="nt">-ListAvailable</span><span class="w">
</span></code></pre></div></div>
<p><img src="/assets\images\Blog\2022-08-24\enabled.png" alt="image-center" class="align-center" /></p>

<p class="notice--info"><strong>Note</strong>
The pending status means that the feature needs to be enabled by Microsoft and NetApp before it can be used.</p>

<p>Bonus: In case you manage providers and its features using Terraform 😞…I did try to enable this feature using terraform but it failed with the below message, which is understandable as it is an opt in feature.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">resource</span> <span class="dl">"</span><span class="s2">azurerm_resource_provider_registration</span><span class="dl">"</span> <span class="dl">"</span><span class="s2">anf</span><span class="dl">"</span> <span class="p">{</span>
   <span class="nx">name</span>     <span class="o">=</span> <span class="dl">"</span><span class="s2">Microsoft.NetApp</span><span class="dl">"</span>
   <span class="nx">feature</span> <span class="p">{</span>
     <span class="nx">name</span>       <span class="o">=</span> <span class="dl">"</span><span class="s2">ANFSDNAppliance</span><span class="dl">"</span>
     <span class="nx">registered</span> <span class="o">=</span> <span class="kc">true</span>
   <span class="p">}</span>
   <span class="nx">feature</span> <span class="p">{</span>
     <span class="nx">name</span>       <span class="o">=</span> <span class="dl">"</span><span class="s2">ANFChownMode</span><span class="dl">"</span>
     <span class="nx">registered</span> <span class="o">=</span> <span class="kc">true</span>
   <span class="p">}</span>
   <span class="nx">feature</span> <span class="p">{</span>
     <span class="nx">name</span>       <span class="o">=</span> <span class="dl">"</span><span class="s2">ANFUnixPermissions</span><span class="dl">"</span>
     <span class="nx">registered</span> <span class="o">=</span> <span class="kc">true</span>
   <span class="p">}</span>
   <span class="nx">feature</span> <span class="p">{</span>
     <span class="nx">name</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">ANFBackupPreview</span><span class="dl">"</span>
     <span class="nx">registered</span> <span class="o">=</span> <span class="kc">true</span>
    <span class="p">}</span>
 <span class="p">}</span>
</code></pre></div></div>
<p><img src="/assets\images\Blog\2022-08-24\tfcprev.png" alt="image-center" class="align-center" /></p>

<h3 id="terraform-configuration-">Terraform Configuration 👨‍💻</h3>
<p>I am deploying ANF using a Module with the AzureRM provider and configuring the backup preview feature using the AzAPI provider as this is an additional configuration to my last [AzApi intro article.][azpiarticle]</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">ANF</span> <span class="nc">Repo</span>
        <span class="o">|</span><span class="n">_Modules</span>
            <span class="o">|</span><span class="n">_ANF_Pool</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">main</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">variables</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">outputs</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>   <span class="o">|</span><span class="n">_</span> <span class="n">ANF_Volume</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">main</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">variables</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">outputs</span><span class="o">.</span><span class="na">tf</span>        
        <span class="o">|</span><span class="n">_</span> <span class="n">main</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span><span class="n">_</span> <span class="n">providers</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span><span class="n">_</span> <span class="n">variables</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span><span class="n">_</span> <span class="n">outputs</span><span class="o">.</span><span class="na">tf</span>
</code></pre></div></div>
<h3 id="enabling-azure-netapp-files-backups-">Enabling Azure NetApp Files Backups 👷</h3>
<p>To configure ANF poicy-based backups for a volume there are some requirements. For more info about them please check <a href="https://docs.microsoft.com/en-us/azure/azure-netapp-files/backup-requirements-considerations">Azure NetApp Files Backup Requirements</a>.</p>

<ul>
  <li>Snapshot policy must be configured and enabled.</li>
  <li>There are some regions with this feature enabled.. So Australia East is one of them for my test.🦸‍♂️</li>
</ul>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">resource</span><span class="w"> </span><span class="s2">"azurerm_netapp_account"</span><span class="w"> </span><span class="s2">"analytics"</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="err">name</span><span class="w">                </span><span class="err">=</span><span class="w"> </span><span class="s2">"cero-netappaccount"</span><span class="w">
  </span><span class="err">location</span><span class="w">            </span><span class="err">=</span><span class="w"> </span><span class="err">data.azurerm_resource_group.one.location</span><span class="w">
  </span><span class="err">resource_group_name</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">data.azurerm_resource_group.one.name</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="err">module</span><span class="w"> </span><span class="s2">"analytics_pools"</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="err">source</span><span class="w">   </span><span class="err">=</span><span class="w"> </span><span class="s2">"./modules/anf_pool"</span><span class="w">

  </span><span class="err">for_each</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">local.pools</span><span class="w">

  </span><span class="err">account_name</span><span class="w">        </span><span class="err">=</span><span class="w"> </span><span class="err">azurerm_netapp_account.analytics.name</span><span class="w">
  </span><span class="err">resource_group_name</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">azurerm_netapp_account.analytics.resource_group_name</span><span class="w">
  </span><span class="err">location</span><span class="w">            </span><span class="err">=</span><span class="w"> </span><span class="err">azurerm_netapp_account.analytics.location</span><span class="w">
  </span><span class="err">volumes</span><span class="w">             </span><span class="err">=</span><span class="w"> </span><span class="err">each.value</span><span class="w">
  </span><span class="err">tags</span><span class="w">                </span><span class="err">=</span><span class="w"> </span><span class="err">var.tags</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>After this deployment, you will be able to see the backup icon as part of the ANF account as below.
<img src="/assets\images\Blog\2022-08-24\backupicon.png" alt="image-center" class="align-center" /></p>

<h4 id="backup-policy-creation-">Backup Policy creation 📝</h4>
<p>After testing for some time I realise the creation of the backup policy is the same as the snapshot policy, it has its own terraform resource as I was getting confused as in the portal it looks like it is part of the ANF account itself. At the end, I use the <code class="language-plaintext highlighter-rouge">azapi_resource</code> resource with the latest API version and the code looks like this:</p>
<ul>
  <li>The parent id is the id of the ANF account.
    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">resource</span><span class="w"> </span><span class="s2">"azapi_resource"</span><span class="w"> </span><span class="s2">"backup_policy"</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">type</span><span class="w">      </span><span class="err">=</span><span class="w"> </span><span class="s2">"Microsoft.NetApp/netAppAccounts/backupPolicies@2022-01-01"</span><span class="w">
</span><span class="err">parent_id</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">azurerm_netapp_account.analytics.id</span><span class="w">
</span><span class="err">name</span><span class="w">      </span><span class="err">=</span><span class="w"> </span><span class="s2">"dailybackup"</span><span class="w">
</span><span class="err">location</span><span class="w">  </span><span class="err">=</span><span class="w"> </span><span class="s2">"australiaeast"</span><span class="w">

</span><span class="err">body</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="err">jsonencode(</span><span class="p">{</span><span class="w">
  </span><span class="err">properties</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="err">enabled</span><span class="w">              </span><span class="err">=</span><span class="w"> </span><span class="kc">true</span><span class="w">
    </span><span class="err">dailyBackupsToKeep</span><span class="w">   </span><span class="err">=</span><span class="w"> </span><span class="mi">1</span><span class="w">
    </span><span class="err">weeklyBackupsToKeep</span><span class="w">  </span><span class="err">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
    </span><span class="err">monthlyBackupsToKeep</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
    <p>Because I am deploying this in Australia East, where there is Availability Zones support. The Azure Blob will use Zone Redundant Storage (ZRS). However, I could not find anything that will provide me that information. What I did saw is that it seems to be using <a href="https://docs.netapp.com/us-en/cloud-manager-backup-restore/task-backup-to-azure.html#quick-start">NetApp Cloud Backup service</a> as the vault is not and Azure Backup Vault and the naming has cbs prefix🕵️♂️ and best of all it feels like CBS. In the Azure Portal (under the volume )it will look like:
<img src="/assets\images\Blog\2022-08-24\cbs.png" alt="image-center" class="align-center" /></p>
  </li>
</ul>

<p class="notice--info"><strong>Note</strong>
Currently ANF backups supports backing up the daily, weekly, and monthly local snapshots created by the associated snapshot policy to the Azure storage. I hope that we can see a more granular backup policy in the future.</p>

<p><img src="/assets\images\Blog\2022-08-24\policy.png" alt="image-center" class="align-center" />
The first snapshot when the backup feature is enabled is called a baseline snapshot, and its name includes the prefix <code class="language-plaintext highlighter-rouge">snapmirror</code>.</p>

<h4 id="assigning-backup-policy-to-an-anf-volume-">Assigning Backup Policy to an ANF Volume 📝</h4>
<p>The next step in the process is to assign the backup policy to an ANF volume. Again, as this is not supported yet by the <code class="language-plaintext highlighter-rouge">AzureRM</code> provider I use the <code class="language-plaintext highlighter-rouge">azapi_update_resource</code> similarly to my last article. In this case, the configuration code looks like below where the data protection block is added to the volume configuration.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">resource</span><span class="w"> </span><span class="s2">"azapi_update_resource"</span><span class="w"> </span><span class="s2">"vol_backup"</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="kr">type</span><span class="w">        </span><span class="o">=</span><span class="w"> </span><span class="s2">"Microsoft.NetApp/netAppAccounts/capacityPools/volumes@2021-10-01"</span><span class="w">
  </span><span class="n">resource_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">module.analytics_pools</span><span class="p">[</span><span class="s2">"pool1"</span><span class="p">]</span><span class="o">.</span><span class="nf">volumes</span><span class="o">.</span><span class="nf">volume1</span><span class="o">.</span><span class="nf">volume</span><span class="o">.</span><span class="nf">id</span><span class="w">
  </span><span class="nx">body</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">jsonencode</span><span class="p">({</span><span class="w">
    </span><span class="n">properties</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="n">dataProtection</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">backup</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="n">backupEnabled</span><span class="w">  </span><span class="o">=</span><span class="w"> </span><span class="n">true</span><span class="w">
          </span><span class="nx">backupPolicyId</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">azapi_resource.backup_policy.id</span><span class="w">
          </span><span class="nx">policyEnforced</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">true</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
      </span><span class="n">unixPermissions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"0740"</span><span class="p">,</span><span class="w">
      </span><span class="n">exportPolicy</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">rules</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[{</span><span class="w">
          </span><span class="n">ruleIndex</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
          </span><span class="n">chownMode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"unRestricted"</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="p">]</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

  </span><span class="p">})</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>The data protection policy will look like the screenshot below… So our Volume is fully protected within the region.
<img src="/assets\images\Blog\2022-08-24\volbackup.png" alt="image-center" class="align-center" /></p>

<p>Something I found odd was that even though the backup policy was already “enabled” when I tried to create my first manual backup it failed, as below indicating it was not honouring accordingly the policy. The solution I found was to disable/re-enable the policy and manual backups started working. I guess is something I will be taking to provide feedback as it is still in preview😶‍🌫️.
<img src="/assets\images\Blog\2022-08-24\failure.png" alt="image-center" class="align-center" /></p>

<h3 id="summary-">Summary 📻</h3>
<p>I can see more adoption using these features more and more in ANF as it was missing to have a more complete Data Protection capability. In regards to the AzAPI provider, once again came to the rescue with its flexibility to provide preview features support for Azure resources. Again, I am keen to see how the <code class="language-plaintext highlighter-rouge">AzAPI2AzureRM</code> migration tool will work in this case when the features are added to the AzureRM provider and see how seamless the experience is. I will be looking into how I can create volumes from a backup and whether there is capability to create manual backups, similar to manual snapshots, using Terraform.</p>

<p>I hope this helps someone and that you find it informative. Please let me know your constructive feedback, as it’s always important. 🕵️‍♂️ That’s it for now—Hasta la vista! 🐱‍🏍</p>

<p><strong>🚴‍♂️ If you enjoyed this blog, you can empower me with some caffeine to continue working on new content. 🚴‍♂️</strong></p>

<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>]]></content><author><name>John Alfaro</name></author><category term="Blog" /><category term="Azure" /><category term="Terraform" /><category term="NetApp" /><category term="Azure NetApp Files" /><category term="ANF" /><category term="TFC" /><summary type="html"><![CDATA[This article is demonstrating how to enable and create backup policies using the AzAPI Terraform provider by leveraging Terraform Cloud for it's deployment]]></summary></entry><entry><title type="html">Deploying Azure NetApp Files Preview Features Using Terraform AzAPI Provider</title><link href="https:/blog.johnalfaro.com/blog/azapi" rel="alternate" type="text/html" title="Deploying Azure NetApp Files Preview Features Using Terraform AzAPI Provider" /><published>2022-06-03T00:00:00+10:00</published><updated>2022-06-03T00:00:00+10:00</updated><id>https:/blog.johnalfaro.com/blog/azapi</id><content type="html" xml:base="https:/blog.johnalfaro.com/blog/azapi"><![CDATA[<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>

<hr />

<p>I was working on a project using Azure NetApp Files, aiming to test some preview features to understand how they can improve capabilities and user experience. The infrastructure was provisioned using Terraform. However, those preview features are not yet enabled in the <a href="https://registry.terraform.io/providers/hashicorp/azurerm">Terraform AzureRM provider</a>. Previously, the workaround was to use ARM templates wrapped with <code class="language-plaintext highlighter-rouge">azurerm_template_deployment</code> to make it Terraform-native, which was not ideal.</p>

<p>Thanks to community feedback and Microsoft’s investment, the provider has improved to release new features and enable private preview ones at a faster cadence. Still, some projects need to enable features as soon as they are released, which is usually possible natively with ARM templates or Bicep.</p>

<p>Microsoft recently released the <a href="https://registry.terraform.io/providers/Azure/azapi">Terraform AzAPI provider</a>, which helps break that barrier in the IaC development process and enables us to deploy features not yet released in the AzureRM provider. The definition is clear, as taken from the <a href="https://github.com/Azure/terraform-provider-azapi">provider GitHub page</a>:</p>

<blockquote>
  <p>The AzAPI provider is a very thin layer on top of the Azure ARM REST APIs. Use this new provider to authenticate to and manage Azure resources and functionality using the Azure Resource Manager APIs directly.</p>
</blockquote>

<p>After playing with the provider, it felt similar to working with Bicep, and I was able to deploy the preview features using the <a href="https://registry.terraform.io/providers/Azure/azapi">AzAPI provider</a> successfully. 👌</p>

<blockquote class="notice--info">
  <p><strong>Note:</strong><br />
The diagram below depicts the architecture to deploy for the test.</p>
</blockquote>

<p><img src="/assets/images/Blog/2022-06-03/anfhl.png" alt="Azure NetApp Files Architecture" class="align-center" /></p>

<hr />

<h3 id="azure-netapp-files-preview-features-️️">Azure NetApp Files Preview Features 🕵️‍♀️</h3>

<p>There are two new features in <a href="https://docs.microsoft.com/en-us/azure/azure-netapp-files/configure-unix-permissions-change-ownership-mode">Azure NetApp Files</a> that are not yet released in the AzureRM provider. These features are related to the NFS Volume or dual-protocol volumes with the <code class="language-plaintext highlighter-rouge">Unix security</code> style, providing more flexibility and control over access to the NFS volumes:</p>

<ul>
  <li><strong>Unix Permissions:</strong> Allows you to specify change permissions for the mount path. The setting does not apply to files under the mount path. By default, permissions are set to <code class="language-plaintext highlighter-rouge">0770</code>, meaning read, write, and execute for owner and group only.</li>
  <li><strong>Change of Ownership (Chown mode):</strong> Enables you to set the ownership management capabilities of files and directories.</li>
</ul>

<blockquote class="notice--info">
  <p><strong>Note:</strong><br />
The Unix permissions you specify apply only to the volume mount point (root directory).<br />
You can modify the Unix permissions on the source volume but not on the destination volume in a cross-region replication configuration.</p>
</blockquote>

<hr />

<h4 id="azure-netapp-files-resource-provider-configuration">Azure NetApp Files Resource Provider Configuration</h4>

<p>To test these new capabilities, you need to enable the features. Depending on how you manage Resource Providers, you can enable these features by adding the following to the <code class="language-plaintext highlighter-rouge">azurerm_resource_provider_registration</code> resource:</p>

<blockquote class="notice--info">
  <p><strong>Note:</strong><br />
This can take ~15 minutes to complete. De-registering the resource provider can take longer than 30 minutes.</p>
</blockquote>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">resource</span> <span class="s2">"azurerm_resource_provider_registration"</span> <span class="s2">"anf"</span> <span class="p">{</span>
  <span class="nx">name</span> <span class="p">=</span> <span class="s2">"Microsoft.NetApp"</span>
  <span class="nx">feature</span> <span class="p">{</span>
    <span class="nx">name</span>       <span class="p">=</span> <span class="s2">"ANFChownMode"</span>
    <span class="nx">registered</span> <span class="p">=</span> <span class="kc">true</span>
  <span class="p">}</span>
  <span class="nx">feature</span> <span class="p">{</span>
    <span class="nx">name</span>       <span class="p">=</span> <span class="s2">"ANFUnixPermissions"</span>
    <span class="nx">registered</span> <span class="p">=</span> <span class="kc">true</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote class="notice--info">
  <p><strong>Important:</strong><br />
Adding or removing preview features will re-register the Resource Provider if managed by Terraform. In addition, you cannot modify a resource provider that is not managed by Terraform as expected.</p>
</blockquote>

<hr />

<h3 id="terraform-configuration-">Terraform Configuration 👨‍💻</h3>

<p>I am deploying ANF using a module with the AzureRM provider and configuring the preview features using the AzAPI provider.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ANF Repo
    |_Modules
        |_ANF_Pool
            |_ main.tf
            |_ variables.tf
            |_ outputs.tf
        |_ ANF_Volume
            |_ main.tf
            |_ variables.tf
            |_ outputs.tf        
    |_ main.tf
    |_ providers.tf
    |_ variables.tf
    |_ outputs.tf
</code></pre></div></div>

<hr />

<h4 id="terraform-azapi-and-azurerm-providers">Terraform AzAPI and AzureRM Providers</h4>

<p>I have declared the providers configuration as below:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">provider</span> <span class="s2">"azurerm"</span> <span class="p">{</span>
  <span class="nx">skip_provider_registration</span> <span class="p">=</span> <span class="kc">true</span>
  <span class="nx">features</span> <span class="p">{}</span>
<span class="p">}</span>

<span class="nx">provider</span> <span class="s2">"azapi"</span> <span class="p">{</span>
<span class="p">}</span>

<span class="nx">terraform</span> <span class="p">{</span>
  <span class="nx">required_providers</span> <span class="p">{</span>
    <span class="nx">azurerm</span> <span class="p">=</span> <span class="p">{</span>
      <span class="nx">source</span>  <span class="p">=</span> <span class="s2">"hashicorp/azurerm"</span>
      <span class="nx">version</span> <span class="p">=</span> <span class="s2">"~&gt; 3.00"</span>
    <span class="p">}</span>
    <span class="nx">azapi</span> <span class="p">=</span> <span class="p">{</span>
      <span class="nx">source</span> <span class="p">=</span> <span class="s2">"azure/azapi"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h3 id="deploying-the-azure-netapp-files-">Deploying the Azure NetApp Files 👷</h3>

<p>I will deploy my ANF structure using the following resources:</p>

<ul>
  <li>ANF Account</li>
  <li>Capacity Pool</li>
  <li>Volume</li>
  <li>Export policy containing one or more export rules that process each client access request</li>
</ul>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">resource</span> <span class="s2">"azurerm_netapp_account"</span> <span class="s2">"analytics"</span> <span class="p">{</span>
  <span class="nx">name</span>                <span class="p">=</span> <span class="s2">"cero-netappaccount"</span>
  <span class="nx">location</span>            <span class="p">=</span> <span class="nx">data</span><span class="err">.</span><span class="nx">azurerm_resource_group</span><span class="err">.</span><span class="nx">one</span><span class="err">.</span><span class="nx">location</span>
  <span class="nx">resource_group_name</span> <span class="p">=</span> <span class="nx">data</span><span class="err">.</span><span class="nx">azurerm_resource_group</span><span class="err">.</span><span class="nx">one</span><span class="err">.</span><span class="nx">name</span>
<span class="p">}</span>

<span class="nx">module</span> <span class="s2">"analytics_pools"</span> <span class="p">{</span>
  <span class="nx">source</span>   <span class="p">=</span> <span class="s2">"./modules/anf_pool"</span>
  <span class="nx">for_each</span> <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">pools</span>

  <span class="nx">account_name</span>        <span class="p">=</span> <span class="nx">azurerm_netapp_account</span><span class="err">.</span><span class="nx">analytics</span><span class="err">.</span><span class="nx">name</span>
  <span class="nx">resource_group_name</span> <span class="p">=</span> <span class="nx">azurerm_netapp_account</span><span class="err">.</span><span class="nx">analytics</span><span class="err">.</span><span class="nx">resource_group_name</span>
  <span class="nx">location</span>            <span class="p">=</span> <span class="nx">azurerm_netapp_account</span><span class="err">.</span><span class="nx">analytics</span><span class="err">.</span><span class="nx">location</span>
  <span class="nx">volumes</span>             <span class="p">=</span> <span class="nx">each</span><span class="err">.</span><span class="nx">value</span>
  <span class="nx">tags</span>                <span class="p">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">tags</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="/assets/images/Blog/2022-06-03/anf.png" alt="ANF Deployment Diagram" class="align-center" /></p>

<blockquote class="notice--info">
  <p><strong>Important:</strong><br />
To create Azure NetApp Files, you need to request access to the region in which you are deploying via a support ticket.</p>
</blockquote>

<hr />

<h4 id="azapi-update-resource-configuration">AzAPI Update Resource Configuration</h4>

<p>Given I already deployed my ANF Account, I use <code class="language-plaintext highlighter-rouge">azapi_update_resource</code> to manage the properties I need from the existing ANF resource properties. It uses the same authentication methods as the AzureRM provider.</p>

<ul>
  <li>Make sure you install the <a href="https://marketplace.visualstudio.com/items?itemName=azapi">Terraform AzAPI provider extension</a> in VS Code, as it will make life easier with IntelliSense completion.</li>
</ul>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">resource</span> <span class="s2">"azapi_update_resource"</span> <span class="s2">"vol_update"</span> <span class="p">{</span>
  <span class="nx">type</span>        <span class="p">=</span> <span class="s2">"Microsoft.NetApp/netAppAccounts/capacityPools/volumes@2021-10-01"</span>
  <span class="nx">resource_id</span> <span class="p">=</span> <span class="nx">module</span><span class="err">.</span><span class="nx">analytics_pools</span><span class="p">[</span><span class="s2">"pool1"</span><span class="p">]</span><span class="err">.</span><span class="nx">volumes</span><span class="err">.</span><span class="nx">volume1</span><span class="err">.</span><span class="nx">volume</span><span class="err">.</span><span class="nx">id</span>
  <span class="nx">body</span> <span class="p">=</span> <span class="nx">jsonencode</span><span class="err">(</span><span class="p">{</span>
    <span class="nx">properties</span> <span class="p">=</span> <span class="p">{</span>
      <span class="nx">unixPermissions</span> <span class="p">=</span> <span class="s2">"0740"</span><span class="err">,</span>
      <span class="nx">exportPolicy</span> <span class="p">=</span> <span class="p">{</span>
        <span class="nx">rules</span> <span class="p">=</span> <span class="p">[{</span>
          <span class="nx">RuleIndex</span> <span class="p">=</span> <span class="mi">1</span><span class="err">,</span>
          <span class="nx">chownMode</span> <span class="p">=</span> <span class="s2">"unRestricted"</span>
        <span class="p">}]</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span><span class="err">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="/assets/images/Blog/2022-06-03/anfcw.png" alt="ANF Volume Update" class="align-center" /></p>

<hr />

<h4 id="azapi-to-azurerm-migration">AzAPI to AzureRM Migration</h4>

<p>I tried to see how this tool would work. However, as shown below, those settings are still not available in the AzureRM provider. I will have to wait to test this with ANF when those features are available in the AzureRM provider. More info about this tool [here][az2rm].</p>

<p><img src="/assets/images/Blog/2022-06-03/azapitoazrm.png" alt="AzAPI to AzureRM Migration" class="align-center" /></p>

<hr />

<h3 id="summary-">Summary 📻</h3>

<p>The AzAPI provider definitely allows us to manage Azure resources and functionality using the Azure ARM REST APIs directly, without having to make complex deployments to leverage Terraform. It gives us the flexibility to manage zero-day feature support for Azure resources.</p>

<p>This is a great example of how to leverage the AzAPI provider to manage Azure resources and increase agility by being able to test new features easily if you are invested in Terraform. Azure NetApp Files keeps improving and adding new features, making it an excellent choice for your data management needs. I am excited to see how this will be used in the future.</p>

<p>I did find a bug in the Azure Portal UI when changing the <code class="language-plaintext highlighter-rouge">chownMode</code> property from <code class="language-plaintext highlighter-rouge">restricted</code> to <code class="language-plaintext highlighter-rouge">unRestricted</code>. This property was not changed in the UI, but it was in the Azure API. The other way around (<code class="language-plaintext highlighter-rouge">unRestricted</code> to <code class="language-plaintext highlighter-rouge">restricted</code>) works fine.</p>

<p>I am eager to see how the <code class="language-plaintext highlighter-rouge">AzAPI2AzureRM</code> migration tool will work when these features are added to the AzureRM provider and how seamless the experience will be.</p>

<p>I hope this helps someone and that you find it informative. Please let me know your constructive feedback, as it’s always important. 🕵️‍♂️ That’s it for now—Hasta la vista! 🐱‍🏍</p>

<p><strong>🚴‍♂️ If you enjoyed this blog, you can empower me with some caffeine to continue working on new content. 🚴‍♂️</strong></p>

<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>]]></content><author><name>John Alfaro</name></author><category term="Blog" /><category term="Azure" /><category term="Terraform" /><category term="NetApp" /><category term="Azure NetApp Files" /><summary type="html"><![CDATA[This article demonstrates how to enable preview features using the AzAPI Terraform provider.]]></summary></entry><entry><title type="html">Federating Google Cloud Identities with Azure Active Directory</title><link href="https:/blog.johnalfaro.com/blog/Federation" rel="alternate" type="text/html" title="Federating Google Cloud Identities with Azure Active Directory" /><published>2022-02-15T00:00:00+11:00</published><updated>2022-02-15T00:00:00+11:00</updated><id>https:/blog.johnalfaro.com/blog/Federation</id><content type="html" xml:base="https:/blog.johnalfaro.com/blog/Federation"><![CDATA[<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>

<hr />

<p>I recently started working with GCP, and as with most public cloud platforms, the first challenge is identity management. GCP allows federation from multiple sources, and if you have a Microsoft stack like Active Directory, you have some great options. In my case, I wanted to test scenarios where identities already exist in Azure AD and leverage its enterprise-level features to establish trust with Google Cloud Identity. This allows us to do the following with minimal effort:</p>

<ol>
  <li><strong>Provision users, groups, and group memberships:</strong><br />
The connector allows you to provision users and groups that are already in Azure AD to Google Cloud Identity.</li>
  <li><strong>Single Sign-On (SSO):</strong><br />
Google Cloud delegates authentication to Azure AD using SAML.</li>
  <li><strong>Conditional Access Policies</strong></li>
  <li><strong>Multifactor Authentication</strong></li>
</ol>

<blockquote class="notice--info">
  <p><strong>Note:</strong><br />
Unfortunately, there is a limitation: Microsoft currently does not allow syncing nested groups through Enterprise Applications. Most enterprises use nested groups, but you cannot sync them through the connector.</p>
</blockquote>

<hr />

<p>The diagram below depicts the flow process for provisioning and Single Sign-On. I used one Enterprise Application for both provisioning and SSO.</p>

<p><img src="/assets/images/Blog/2022-02-15/identity.png" alt="Google Cloud Identity Federation Flow" class="align-center" /></p>

<hr />

<h3 id="google-requirements">Google Requirements</h3>

<p>You need to create a user account in the <a href="https://admin.google.com/">GCP Admin Console</a>. For my PoC, it is <code class="language-plaintext highlighter-rouge">ceroprov@johnalfaro.com</code>, and it needs <code class="language-plaintext highlighter-rouge">super-admin</code> permissions. This account will authorize the creation of groups and users on the GCP side.</p>

<p><img src="/assets/images/Blog/2022-02-15/prov.png" alt="GCP Admin Console" class="align-center" /></p>

<hr />

<h3 id="azure-ad-requirements">Azure AD Requirements</h3>

<p>There are a few settings to configure in Azure AD to enable the Enterprise Application for both user provisioning and SSO. Ensure you have the correct permissions. The following roles (from highest to lowest) can perform these tasks: <code class="language-plaintext highlighter-rouge">Global Administrator</code>, <code class="language-plaintext highlighter-rouge">Cloud Application Administrator</code>, or <code class="language-plaintext highlighter-rouge">Application Administrator</code>.</p>

<p><img src="/assets/images/Blog/2022-02-15/entapp.png" alt="Azure Enterprise Application" class="align-center" /></p>

<ol>
  <li><strong>Create the Google Cloud Connector Enterprise Application:</strong><br />
Search for <code class="language-plaintext highlighter-rouge">Google Cloud</code> and select <code class="language-plaintext highlighter-rouge">Google Cloud/G Suite Connector by Microsoft</code>. Configure the following:
    <ul>
      <li>Set <code class="language-plaintext highlighter-rouge">Enabled for users to sign-in</code> to <code class="language-plaintext highlighter-rouge">Yes</code>.</li>
      <li>Set <code class="language-plaintext highlighter-rouge">User assignment required</code> to <code class="language-plaintext highlighter-rouge">Yes</code>.</li>
      <li>Set <code class="language-plaintext highlighter-rouge">Visible to users</code> to <code class="language-plaintext highlighter-rouge">No</code>.</li>
    </ul>
  </li>
  <li><strong>Provisioning Settings:</strong>
    <ul>
      <li>Set <code class="language-plaintext highlighter-rouge">Provisioning Mode</code> to <code class="language-plaintext highlighter-rouge">Automatic</code>.</li>
      <li>Set <code class="language-plaintext highlighter-rouge">Admin Credentials</code> and then <code class="language-plaintext highlighter-rouge">Authorize</code>.<br />
Use the <code class="language-plaintext highlighter-rouge">ceroprov@johnalfaro.com</code> account to authorize the application. Allowing this confirms access to the GCP Cloud Identity API.
<img src="/assets/images/Blog/2022-02-15/aad_to_gcp.png" alt="Provisioning Settings" class="align-center" /></li>
    </ul>
  </li>
  <li><strong>Test the connection</strong> to ensure you can authenticate to the API.
<img src="/assets/images/Blog/2022-02-15/testcon.png" alt="Test Connection" class="align-center" /></li>
</ol>

<blockquote class="notice--info">
  <p><strong>Important:</strong><br />
Always keep security in mind and use credentials with the least privileged access.</p>
</blockquote>

<hr />

<h3 id="configure-usergroup-provisioning">Configure User/Group Provisioning</h3>

<p>In Azure AD, configure the mapping of users and groups to the GCP Cloud Identity API.</p>

<h4 id="user-provisioning">User Provisioning</h4>

<p>I used the <code class="language-plaintext highlighter-rouge">UPN</code> to configure user mapping. This is the unique identifier in Azure AD. The <a href="https://cloud.google.com/architecture/identity/federating-gcp-with-azure-ad-configuring-provisioning-and-single-sign-on#configure_user_provisioning">GCP documentation</a> provides details on mapping.</p>

<ol>
  <li>Under <code class="language-plaintext highlighter-rouge">attribute mapping</code>, select the row <code class="language-plaintext highlighter-rouge">surname</code> and set <code class="language-plaintext highlighter-rouge">Default value if null</code> to <code class="language-plaintext highlighter-rouge">_</code>.</li>
  <li>Under <code class="language-plaintext highlighter-rouge">attribute mapping</code>, select the row <code class="language-plaintext highlighter-rouge">givenName</code> and set <code class="language-plaintext highlighter-rouge">Default value if null</code> to <code class="language-plaintext highlighter-rouge">_</code>.</li>
</ol>

<h4 id="group-provisioning">Group Provisioning</h4>

<p>I used the <code class="language-plaintext highlighter-rouge">Name</code> to configure group mapping. The <a href="https://cloud.google.com/architecture/identity/federating-gcp-with-azure-ad-configuring-provisioning-and-single-sign-on#configure_user_provisioning">GCP documentation</a> recommends editing the <code class="language-plaintext highlighter-rouge">mail</code> attribute in <code class="language-plaintext highlighter-rouge">attribute mappings</code>, changing the mapping type to <code class="language-plaintext highlighter-rouge">Expression</code>, and setting the expression as below. Replace <code class="language-plaintext highlighter-rouge">johnalfaro.com</code> with your registered domain.</p>

<p>I also set <code class="language-plaintext highlighter-rouge">Sync only assigned users and groups</code> to avoid syncing all users and groups, which is recommended for any organization.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Join</span><span class="p">(</span><span class="s2">"@"</span><span class="p">,</span> <span class="no">NormalizeDiacritics</span><span class="p">(</span><span class="no">StripSpaces</span><span class="p">([</span><span class="n">displayName</span><span class="p">])),</span> <span class="s2">"johnalfaro.com"</span><span class="p">)</span>
</code></pre></div></div>

<p><img src="/assets/images/Blog/2022-02-15/mapp.png" alt="Group Mapping" class="align-center" /></p>

<hr />

<h4 id="user-and-group-syncing">User and Group Syncing</h4>

<p>Now it’s time to test the connector. Add relevant users and groups you want to sync to GCP under <code class="language-plaintext highlighter-rouge">Manage &gt; Users and Groups</code>. You have two options to sync:</p>

<ol>
  <li><strong><a href="https://docs.microsoft.com/en-us/azure/active-directory/app-provisioning/provision-on-demand">Provision on demand</a>:</strong><br />
This manual process is useful for testing, troubleshooting, and validating attribute mapping expressions.
<img src="/assets/images/Blog/2022-02-15/provondemand.png" alt="Provision on Demand" class="align-center" /></li>
  <li><strong>Automatic Provisioning:</strong><br />
This is the recommended option. It provisions users and groups in the GCP Cloud Identity API. There is an initial provisioning cycle, followed by incremental cycles every 40 minutes.</li>
</ol>

<hr />

<h4 id="monitoring">Monitoring</h4>

<p>You can monitor the provisioning process out-of-the-box. The logs provide details about all operations run by the user provisioning service, including provisioning status for individual users and groups.</p>

<p><img src="/assets/images/Blog/2022-02-15/logs.png" alt="Provisioning Logs" class="align-center" /></p>

<hr />

<h3 id="single-sign-on-configuration">Single Sign-On Configuration</h3>

<p>I followed the GCP documentation to set up SSO. The configuration is as follows:</p>

<ol>
  <li>Edit the <code class="language-plaintext highlighter-rouge">Basic SAML Configuration</code> with:
    <ul>
      <li>Identifier (Entity ID): <code class="language-plaintext highlighter-rouge">google.com</code></li>
      <li>Reply URL: <code class="language-plaintext highlighter-rouge">https://www.google.com/</code></li>
      <li>Sign on URL: <code class="language-plaintext highlighter-rouge">https://www.google.com/a/johnalfaro/ServiceLogin?continue=https://console.cloud.google.com/</code> (replace <code class="language-plaintext highlighter-rouge">johnalfaro</code> with your domain name)</li>
    </ul>
  </li>
  <li>Download the <code class="language-plaintext highlighter-rouge">Certificate (Base64)</code> from the <code class="language-plaintext highlighter-rouge">SAML Signing Certificate</code>.</li>
  <li>In <code class="language-plaintext highlighter-rouge">Attributes &amp; Claims</code>, select <code class="language-plaintext highlighter-rouge">user.userprincipalname</code> as the Unique identifier.</li>
  <li>In the <a href="https://admin.google.com/">GCP Admin Console</a>, log in as a Super-admin and navigate to <code class="language-plaintext highlighter-rouge">Security &gt; Authentication &gt; SSO with third-party IdP</code> then <code class="language-plaintext highlighter-rouge">Add SSO profile</code>. Enable <code class="language-plaintext highlighter-rouge">Setup SSO with third party identity provider</code> and configure:
    <ul>
      <li>Sign-in page URL: Copy from the <code class="language-plaintext highlighter-rouge">Set up Google Cloud</code> card in the Google Enterprise Application SSO Configuration.</li>
      <li>Sign-out page URL: <code class="language-plaintext highlighter-rouge">https://login.microsoftonline.com/common/wsfederation?wa=wsignout1.0</code></li>
      <li>Change password URL: <code class="language-plaintext highlighter-rouge">https://account.activedirectory.windowsazure.com/changepassword.aspx</code> (depends on SSPR usage)</li>
      <li>Upload the certificate in the <code class="language-plaintext highlighter-rouge">verification certificate</code> field.</li>
    </ul>
  </li>
</ol>

<p><img src="/assets/images/Blog/2022-02-15/sso.png" alt="SSO Configuration" class="align-center" /></p>

<hr />

<h3 id="the-test-drive-️">The Test-Drive 🦸‍♂️</h3>

<p>After setup, I tested the connector by adding users and groups to sync to GCP and logging in to the <a href="https://console.cloud.google.com/">GCP Console</a>. This redirected me to Azure AD for sign-in, and after successful authentication, I was redirected to the GCP Console.</p>

<p><img src="/assets/images/Blog/2022-02-15/sso_gcp.gif" alt="SSO Test" class="align-center" /></p>

<hr />

<h3 id="summary">Summary</h3>

<p>The Google documentation was easy to follow, and the setup was straightforward in a demo environment. I did encounter some issues with provisioning, such as occasional quarantining during automatic provisioning (with email notifications). At the enterprise level, I would prefer to provision users and groups in GCP using the API to ensure effective syncing, especially since nested groups are not supported by Azure AD provisioning.</p>

<p>I also enjoyed writing this in markdown and using <a href="https://copilot.github.com/">GitHub Copilot</a> to help draft the blog, which reduced typos. 👨‍💻</p>

<p>I hope this helps someone and that you find it informative. Please let me know your constructive feedback, as it’s always important. 🕵️‍♂️ That’s it for now—Hasta la vista! 🐱‍🏍</p>

<p><strong>🚴‍♂️ If you enjoyed this blog, you can empower me with some caffeine to continue working on new content. 🚴‍♂️</strong></p>

<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>]]></content><author><name>John Alfaro</name></author><category term="Blog" /><category term="Azure Active Directory" /><category term="Google Cloud" /><category term="Identity Federation" /><summary type="html"><![CDATA[This article demonstrates how to map Azure AD identities to Google Cloud Identity.]]></summary></entry><entry><title type="html">Continuous Deployment Recipe using GitHub Actions, Checkov and Terraform Cloud(CLI)</title><link href="https:/blog.johnalfaro.com/blog/GH_CICD" rel="alternate" type="text/html" title="Continuous Deployment Recipe using GitHub Actions, Checkov and Terraform Cloud(CLI)" /><published>2021-09-01T00:00:00+10:00</published><updated>2021-09-01T00:00:00+10:00</updated><id>https:/blog.johnalfaro.com/blog/GH_CICD</id><content type="html" xml:base="https:/blog.johnalfaro.com/blog/GH_CICD"><![CDATA[<meta name="image" property="og:image" content="https://blog.johnalfaro.com/assets/images/splash.jpg" />

<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>

<p>In my last article I was working with a CI/CD pipeline using <a href="https://blog.johnalfaro.com/blog/CICD/">Azure Pipelines</a>. This time I wanted to use the same approach but using <a href="https://docs.github.com/en/actions">GitHub Actions</a> which lately is the new trend and also it is known that Microsoft is investing a bit more around this product. I must say I was impressed on how fast I was a able to test it out.</p>

<p>I wanted to Test-Drive this model by using the Terraform Cloud <a href="https://www.terraform.io/docs/cloud/run/cli.html">CLI-driven Run Workflow</a> instead of the API-driven Run Workflow for a change. Also, I have introduced some Static Code analysis using <a href="https://www.checkov.io/">Checkov</a>. Checkov is a static code analysis tool for scanning infrastructure as code (IaC) files for misconfigurations that may lead to security or compliance problems, so I was able to plug that in into my workflow as it has a Github Action too.
<img src="/assets\images\Blog\2021-09-01\gha.jpg" alt="drawing" style="width:2600px;" /></p>
<h2 id="environment-preparation">Environment preparation</h2>
<p>What do I need to get started?</p>

<h3 id="terraform-cloud">Terraform Cloud</h3>
<p>You need a Terraform Cloud Instance <a href="https://www.hashicorp.com/products/terraform/pricing">Free plan</a> up to 5 users and you can leverage the <code class="language-plaintext highlighter-rouge">Private Module registry</code> and <code class="language-plaintext highlighter-rouge">Remote State Storage</code>.</p>
<ol>
  <li>
    <p>Create a workspace using the CLI workflow and assign the required variables to authenticate to the Azure Platform
<img src="/assets\images\Blog\2021-09-01\tfcenv.jpg" alt="image-center" class="align-center" /></p>
  </li>
  <li>
    <p>Generate a token that will be used by GitHub to authenticate to TFC and trigger the workspace for the Infrastructure as Code to be deployed
<img src="/assets\images\Blog\2021-09-01\tokentfc.jpg" alt="image-center" class="align-center" /></p>
  </li>
</ol>

<h3 id="github-repository">GitHub Repository</h3>
<p>Create a new Github Repo.</p>
<ol>
  <li>Go to your GitHub profile then <code class="language-plaintext highlighter-rouge">settings</code> then <code class="language-plaintext highlighter-rouge">developer settings</code> then <code class="language-plaintext highlighter-rouge">personal access tokens</code>
    <ul>
      <li>Generate a GitHub token. This is to be used for commenting the PR’s for review 
<img src="/assets\images\Blog\2021-09-01\ghtoken.jpg" alt="image-center" class="align-center" /></li>
    </ul>
  </li>
  <li>Go to <code class="language-plaintext highlighter-rouge">Settings</code> then <code class="language-plaintext highlighter-rouge">Secrets</code>
    <ul>
      <li>Create a new secret named <code class="language-plaintext highlighter-rouge">TFC_TOKEN</code> and paste the TFC token value generated.</li>
      <li>Create a secret called <code class="language-plaintext highlighter-rouge">AZURE_CREDENTIALS</code>. This is for the Web App deployment. Make sure you set the <code class="language-plaintext highlighter-rouge">JSON</code> format properly as you may get some errors. It should look like this.
        <pre><code class="language-JSON">{
    "clientId": "&lt;GUID&gt;",
    "clientSecret": "&lt;GUID&gt;",
    "subscriptionId": "&lt;GUID&gt;",
    "tenantId": "&lt;GUID&gt;",
}
</code></pre>
      </li>
    </ul>
  </li>
</ol>

<p>🤜Looks like we got everything required regarding Authentication &amp; Authorization🤛
<img src="/assets\images\Blog\2021-09-01\secrets.jpg" alt="image-center" class="align-center" /></p>

<p class="notice--info"><strong>Important</strong> Always keep security in mind and use credentials with less privileged access.</p>

<p>My repo is organized like this to make simple</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Web</span> <span class="nc">App</span> <span class="nc">Repo</span>
        <span class="o">|</span>
        <span class="o">|</span><span class="n">_</span><span class="o">.</span><span class="na">github</span><span class="err">\</span><span class="n">workflows</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_terraform</span><span class="o">.</span><span class="na">yml</span>
        <span class="o">|</span>        
        <span class="o">|</span><span class="n">_Terraform</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">main</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">variables</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">outputs</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">backend</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">provider</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>        
        <span class="o">|</span><span class="n">_</span> <span class="n">src</span>
            <span class="o">|</span><span class="n">_</span> <span class="nc">Application</span> <span class="nc">Source</span> <span class="nc">Code</span>
</code></pre></div></div>
<h2 id="deployment-process">Deployment Process</h2>
<p>I have created a workflow with three GitHub Actions which actually were already available for its consumptionn in the <a href="https://github.com/marketplace">GitHub Actions Marketplace</a> one for the <a href="https://github.com/marketplace/actions/hashicorp-setup-terraform">infrastructure Deployment</a>, one for <a href="https://github.com/marketplace/actions/checkov-github-action">Static Code Analysis</a> and one for the <a href="https://github.com/marketplace/actions/azure-webapp">Web App</a> code deployment. A difference to my article using AZDO, this time I am creating a Pull Request to do some validation in my terraform files prior applying the configuration.</p>
<ol>
  <li>After code has been committed a Pull Request will be created, consequently a validation process will kick off. All results will be directly available in the Pull Request instead of opening the GitHub Action or the Terraform Cloud workspace. However, on the time I played with I could not get Checkov’s output to be advertised in the PR comments. I think if using the commands directly and installing the binary it will be more flexible ಥ_ಥ.
    <ul>
      <li>Terraform format: checks whether the configuration has been properly formatted</li>
      <li>Terraform Validate: validates the configuration used in the GitHub action workflow.</li>
      <li>Terraform plan: generates a Terraform plan in the Terraform Cloud workspace</li>
      <li>Static Security Code Analysis: Potential compliance/misconfiguration
<img src="/assets\images\Blog\2021-09-01\pr.jpg" alt="image-center" class="align-center" /></li>
    </ul>
  </li>
</ol>

<h3 id="static-security-code-analysis-with-checkov">Static Security Code Analysis with Checkov</h3>
<p>Interestingly enough, and after a few rounds I had to set this variable <code class="language-plaintext highlighter-rouge">soft_fail: true</code> as it does not seem to be a way to select the policies that are important for your environment OOB. However, I found there are more policies for compliance than tfsec for my specific case. The policies as you can see below are good. However, some of them could potentially be false positives and/or not required depending on your environment. Still this is great tool and I think I will be exploring deeper into this project.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="o">-</span> <span class="ss">name: </span><span class="no">Run</span> <span class="no">Checkov</span> <span class="n">action</span>
      <span class="ss">id: </span><span class="n">checkov</span>
      <span class="ss">uses: </span><span class="n">bridgecrewio</span><span class="o">/</span><span class="n">checkov</span><span class="o">-</span><span class="n">action</span><span class="vi">@master</span>
      <span class="ss">with:
        directory: </span><span class="n">terraform</span><span class="o">/</span>
        <span class="ss">soft_fail: </span><span class="kp">true</span>
</code></pre></div></div>
<p><img src="/assets\images\Blog\2021-09-01\PR_Checks.jpg" alt="image-center" class="align-center" /></p>

<ol>
  <li>When merging the PR, it will trigger the deployment of the Infrastructure and the Web App deployment (I know I didn’t do any validation on the web app code ☜(ﾟヮﾟ☜))… and the final result is a deployed Web App in about ~10 mins.
<img src="/assets\images\Blog\2021-09-01\end.jpg" alt="image-center" class="align-center" /></li>
</ol>

<h2 id="github-workflow">GitHub Workflow</h2>
<p>The GitHub workflow in this case is distributed in one job with different multiple steps that will run based on the branch and github event.
<img src="/assets\images\Blog\2021-09-01\workflow.jpg" alt="image-center" class="align-center" /></p>

<h2 id="infrastructure-as-code-deployment-stage">Infrastructure as Code Deployment Stage</h2>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jobs</span>
  <span class="ss">terraform:
    name: </span><span class="s1">'Terraform'</span>
    <span class="n">runs</span><span class="o">-</span><span class="ss">on: </span><span class="n">ubuntu</span><span class="o">-</span><span class="n">latest</span>

    <span class="c1"># Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest</span>
    <span class="ss">defaults:
      run:
        shell: </span><span class="n">bash</span>
        <span class="n">working</span><span class="o">-</span><span class="ss">directory: </span><span class="n">terraform</span>

    <span class="ss">steps:
    </span><span class="c1"># Checkout the repository to the GitHub Actions runner</span>
    <span class="o">-</span> <span class="ss">name: </span><span class="no">Checkout</span>
      <span class="ss">uses: </span><span class="n">actions</span><span class="o">/</span><span class="n">checkout</span><span class="vi">@v2</span>

    <span class="c1"># Install the latest version of Terraform CLI and configure the Terraform CLI configuration file with a Terraform Cloud user API token</span>
    <span class="o">-</span> <span class="ss">name: </span><span class="no">Setup</span> <span class="no">Terraform</span>
      <span class="ss">uses: </span><span class="n">hashicorp</span><span class="o">/</span><span class="n">setup</span><span class="o">-</span><span class="n">terraform</span><span class="vi">@v1</span>
      <span class="ss">with:
        cli_config_credentials_token: </span><span class="err">$</span>

    <span class="c1"># Checks that all Terraform configuration files adhere to a canonical format</span>
    <span class="o">-</span> <span class="ss">name: </span><span class="no">Terraform</span> <span class="no">Format</span>
      <span class="ss">id: </span><span class="n">fmt</span>
      <span class="ss">run: </span><span class="n">terraform</span> <span class="n">fmt</span> <span class="o">-</span><span class="n">check</span>

    <span class="c1"># Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc.</span>
    <span class="o">-</span> <span class="ss">name: </span><span class="no">Terraform</span> <span class="no">Init</span>
      <span class="ss">id: </span><span class="n">init</span>
      <span class="ss">run: </span><span class="n">terraform</span> <span class="n">init</span>

    <span class="o">-</span> <span class="ss">name: </span><span class="no">Terraform</span> <span class="no">Validate</span>
      <span class="ss">id: </span><span class="n">validate</span>
      <span class="ss">run: </span><span class="n">terraform</span> <span class="n">validate</span> <span class="o">-</span><span class="n">no</span><span class="o">-</span><span class="n">color</span>

    <span class="c1"># Generates an execution plan for Terraform</span>
    <span class="o">-</span> <span class="ss">name: </span><span class="no">Terraform</span> <span class="no">Plan</span>
      <span class="ss">id: </span><span class="n">plan</span>
      <span class="ss">if: </span><span class="n">github</span><span class="p">.</span><span class="nf">event_name</span> <span class="o">==</span> <span class="s1">'pull_request'</span>
      <span class="ss">run: </span><span class="n">terraform</span> <span class="n">plan</span> <span class="o">-</span><span class="n">no</span><span class="o">-</span><span class="n">color</span>
      <span class="n">continue</span><span class="o">-</span><span class="n">on</span><span class="o">-</span><span class="ss">error: </span><span class="kp">true</span>

    <span class="o">-</span> <span class="ss">uses: </span><span class="n">actions</span><span class="o">/</span><span class="n">github</span><span class="o">-</span><span class="n">script</span><span class="vi">@v4</span><span class="o">.</span><span class="mf">1.0</span>
      <span class="ss">if: </span><span class="n">github</span><span class="p">.</span><span class="nf">event_name</span> <span class="o">==</span> <span class="s1">'pull_request'</span>
      <span class="ss">env:
          </span><span class="no">PLAN</span><span class="p">:</span> <span class="s2">"terraform</span><span class="se">\n</span><span class="s2">$"</span>
      <span class="ss">with:
          </span><span class="n">github</span><span class="o">-</span><span class="ss">token: </span><span class="err">$</span>
          <span class="ss">script: </span><span class="o">|</span>
            <span class="n">const</span> <span class="n">output</span> <span class="o">=</span> <span class="sb">`#### Terraform Format and Style 🖌</span><span class="se">\`</span><span class="sb">$</span><span class="se">\`</span><span class="sb">
            #### Terraform Initialization ⚙️</span><span class="se">\`</span><span class="sb">$</span><span class="se">\`</span><span class="sb">
            #### Terraform Validation 🤖</span><span class="se">\`</span><span class="sb">$</span><span class="se">\`</span><span class="sb">
            #### Terraform Plan 📖</span><span class="se">\`</span><span class="sb">$</span><span class="se">\`</span><span class="sb">
            #### Static Security Analysis 🕵️‍♀️</span><span class="se">\`</span><span class="sb">$</span><span class="se">\`</span><span class="sb">
            
            &lt;details&gt;&lt;summary&gt;Show Plan&lt;/summary&gt;

            </span><span class="se">\`\`\`\n</span><span class="sb">
            ${process.env.PLAN}
            </span><span class="se">\`\`\`</span><span class="sb">

            &lt;/details&gt;

            *Pusher: @$, Action: </span><span class="se">\`</span><span class="sb">$</span><span class="se">\`</span><span class="sb">*`</span><span class="p">;</span>
           
            <span class="n">github</span><span class="p">.</span><span class="nf">issues</span><span class="p">.</span><span class="nf">createComment</span><span class="p">({</span>
              <span class="ss">issue_number: </span><span class="n">context</span><span class="p">.</span><span class="nf">issue</span><span class="p">.</span><span class="nf">number</span><span class="p">,</span>
              <span class="ss">owner: </span><span class="n">context</span><span class="p">.</span><span class="nf">repo</span><span class="p">.</span><span class="nf">owner</span><span class="p">,</span>
              <span class="ss">repo: </span><span class="n">context</span><span class="p">.</span><span class="nf">repo</span><span class="p">.</span><span class="nf">repo</span><span class="p">,</span>
              <span class="ss">body: </span><span class="n">output</span>
            <span class="p">})</span>

    <span class="o">-</span> <span class="ss">name: </span><span class="no">Terraform</span> <span class="no">Plan</span> <span class="no">Status</span>
      <span class="ss">if: </span><span class="n">steps</span><span class="p">.</span><span class="nf">plan</span><span class="p">.</span><span class="nf">outcome</span> <span class="o">==</span> <span class="s1">'failure'</span>
      <span class="ss">run: </span><span class="nb">exit</span> <span class="mi">1</span>

      <span class="c1"># On push to main, build or change infrastructure according to Terraform configuration files</span>
      <span class="c1"># Note: It is recommended to set up a required "strict" status check in your repository for "Terraform Cloud". See the documentation on "strict" required status checks for more information: https://help.github.com/en/github/administering-a-repository/types-of-required-status-checks</span>
    <span class="o">-</span> <span class="ss">name: </span><span class="no">Terraform</span> <span class="no">Apply</span>
      <span class="ss">if: </span><span class="n">github</span><span class="p">.</span><span class="nf">ref</span> <span class="o">==</span> <span class="s1">'refs/heads/main'</span> <span class="o">&amp;&amp;</span> <span class="n">github</span><span class="p">.</span><span class="nf">event_name</span> <span class="o">==</span> <span class="s1">'push'</span>
      <span class="ss">run: </span><span class="n">terraform</span> <span class="n">apply</span> <span class="o">-</span><span class="n">auto</span><span class="o">-</span><span class="n">approve</span>   
</code></pre></div></div>

<h3 id="infrastructure-as-code-deployment-in-action">Infrastructure as Code deployment in Action</h3>
<p>The below is a quick demo of the Github Actions and Terraform Cloud interaction triggered via CLI to successfully deploy the Infrastructure required with minimum effort</p>

<p><img src="/assets\images\Blog\2021-09-01\TFC_GH.gif" alt="image-center" class="align-center" /></p>

<h2 id="web-app-deployment">Web App Deployment</h2>
<p>The below describes the steps required to build and deploy the code in the Azure Web App, This is basd on the github action OOB.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    - uses: azure/login@v1
      if: github.ref == 'refs/heads/main' &amp;&amp; github.event_name == 'push'
      with:
        creds: $
          
    - name: Setup Node $
      uses: actions/setup-node@v1
      if: github.ref == 'refs/heads/main' &amp;&amp; github.event_name == 'push'
      with:
        node-version: $
      
    - name: 'npm install, build, and test'
      if: github.ref == 'refs/heads/main' &amp;&amp; github.event_name == 'push'
      run: |
          npm install
          npm run build
                
      # deploy web app using Azure credentials
    - uses: azure/webapps-deploy@v2
      if: github.ref == 'refs/heads/main' &amp;&amp; github.event_name == 'push'
      with:
        app-name: $
        package: $

      # Azure logout 
    - name: logout
      if: github.ref == 'refs/heads/main' &amp;&amp; github.event_name == 'push'
      run: |
          az logout
</code></pre></div></div>

<h2 id="summary">Summary</h2>

<p>🤔My idea in this article was to share my first experience with GitHub Actions “translating” what I had for Azure DevOps and see the similarities and new opportunities that it may open. I really like that I was able to do all of this with not much effort, as there are many actions available in the Github Actions marketplace. I can see many new projects are invested in GitHub Actions to provide their product integrations. I also liked the speed to get GitHub runner compared to a Microsoft Hosted agent in AZDO</p>

<p>In regards to Checkov, I would love to have more flexibility on the policies as it may be hard to get one size to fit all. Still I have to dig a bit more but it can definitely a good tool to use for static code analysis. Terraform Sentinel can help with this, you still have to author all the policies and that could be tedious, especially if golang is not your cup of tea ☕. However, you have the flexibility. Checkov is Python based which for me can be easier to digest 👨‍💻,, so what is your preference??</p>

<p>Overall the experience was great with some small challenges but the documentation is good to get your head around it. Especially with the action gives you quite a good percentage of the heavy lifting 🦸‍♂️</p>

<p>I do hope this helps someone and that you find it informative,, so please let me know as constructive feedback is always important🕵️‍♂️,, That’s it for now,,, Hasta la vista🐱‍🏍!!!</p>

<p><strong>🚴‍♂️ If you enjoyed this blog, you can empower me with some caffeine to continue working in new content 🚴‍♂️.</strong></p>

<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>]]></content><author><name>John Alfaro</name></author><category term="Blog" /><category term="Azure Web App" /><category term="GitHub Actions" /><category term="Terraform Cloud" /><category term="Checkov" /><summary type="html"><![CDATA[This article is demonstrating a deployment using GitHub Actions and leveraging Checkov for Security Code Analisys and Terraform Cloud CLI-driven Run Workflow for an Azure Web App]]></summary></entry><entry><title type="html">A continuous Deployment Recipe consuming Azure DevOps 👉 PowerShell 👉 Terraform Cloud(API-driven Run Workflow)</title><link href="https:/blog.johnalfaro.com/blog/CICD" rel="alternate" type="text/html" title="A continuous Deployment Recipe consuming Azure DevOps 👉 PowerShell 👉 Terraform Cloud(API-driven Run Workflow)" /><published>2021-07-18T00:00:00+10:00</published><updated>2021-07-18T00:00:00+10:00</updated><id>https:/blog.johnalfaro.com/blog/CICD</id><content type="html" xml:base="https:/blog.johnalfaro.com/blog/CICD"><![CDATA[<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>

<p>I have been working with Terraform Cloud/Enterprise using the Version Control System <a href="https://www.terraform.io/docs/cloud/workspaces/vcs.html">VCS</a> which is good but to my opinion does not provide enough flexibility when you want to have a proper Continuous Deployment flow where you are not just deploying Infrastructure, but you are covering the cycle from Continuous Integration and ending with a Release of verified artefacts/packages or even container images. <a href="https://docs.microsoft.com/en-us/devops/deliver/what-is-continuous-delivery">Continuous Deployment</a> guarantee that qualified releases are automatically deployed to Production.</p>

<p>So I wanted to Test-Drive this model by using the Terraform Cloud <a href="https://www.terraform.io/docs/cloud/run/api.html">API-driven Run Workflow</a> by not associating a workspace to a VCS repo, instead using Azure DevOps Pipeline to decide when a configuration should be changed and when runs should occur.</p>
<h2 id="environment-preparation">Environment preparation</h2>
<p>What do I need to get started?</p>

<ol>
  <li>Azure DevOps Project, you can use the <a href="https://azure.microsoft.com/en-au/pricing/details/devops/azure-devops-services/">Free Tier</a>.</li>
  <li>Terraform Cloud Instance <a href="https://www.hashicorp.com/products/terraform/pricing">Free plan</a> up to 5 users and you can leverage the <code class="language-plaintext highlighter-rouge">Private Module registry</code> and <code class="language-plaintext highlighter-rouge">Remote State Storage</code>.</li>
  <li>Some code to deploy to the Web App, here I am deploying a Gatsby Site that can be found on the Microsoft learn modules.</li>
</ol>

<p>My repo is organized like this to make simple</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Web</span> <span class="nc">App</span> <span class="nc">Repo</span>
        <span class="o">|</span><span class="n">_Terraform</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">main</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">variables</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">outputs</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">backend</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>       <span class="o">|</span><span class="n">_</span> <span class="n">provider</span><span class="o">.</span><span class="na">tf</span>
        <span class="o">|</span>        
        <span class="o">|</span><span class="n">_</span> <span class="n">src</span>
        <span class="o">|</span>    <span class="o">|</span><span class="n">_</span> <span class="nc">Application</span> <span class="nc">Source</span> <span class="nc">Code</span>
        <span class="o">|</span>
        <span class="o">|</span><span class="n">_TFC</span><span class="o">.</span><span class="na">ps1</span>
</code></pre></div></div>
<h2 id="deployment-process">Deployment Process</h2>
<p>I have created a pipeline with three stages as per diagram.</p>
<ol>
  <li>Builds the application source code to be deployed on the Web App and deploys the infrastructure by triggering a Terraform Cloud Workspace using the API</li>
  <li>Deploy the artifact coming from the build to the Web App Staging slot if successful, there will be a pre-deployment approval that will be required before it goes to Production. Providing a timeout of 24hrs before it expires.</li>
  <li>If approval given after validating the app changes in the staging slot.  It will be executing the <a href="https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots#swap-operation-steps">Slot Swap</a> with the Production slot</li>
</ol>

<p class="notice--info"><strong>Just in case</strong> The staging slot will now have the previous production app.</p>
<p><img src="/assets\images\Blog\2021-07-18\AZDO-TFC.jpg" alt="image-center" class="align-center" /></p>

<h2 id="azure-devops-pipeline">Azure DevOps Pipeline</h2>
<p>I have setup the pipeline steps as below in order to demo the deployment. For the first step, I am triggering a PowerShell Script that will trigger the deployment on my Terraform Cloud Workspace in this case. I was using the built-in PowerShell task. However, it was using PowerShell v5.1 and not PowerShell v7, hence I used the PowerShell v2 task and explicitly declared to use PowerShell Core by using the Boolean <code class="language-plaintext highlighter-rouge">true</code>. Additionally, I stored the API token generated in Terraform Cloud as a secret value variable for the interaction with my workspace
<img src="/assets\images\Blog\2021-07-18\stages.jpg" alt="image-center" class="align-center" /></p>

<h2 id="infrastructure-as-code-deployment-stage">Infrastructure as Code Deployment Stage</h2>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">-</span> <span class="ss">stage: </span><span class="no">IaC</span>
  <span class="ss">displayName: </span><span class="no">Infrastructure</span> <span class="no">Deployment</span>
  <span class="ss">jobs:
  </span><span class="o">-</span> <span class="ss">job: </span><span class="no">Build</span>
    <span class="ss">steps:
      </span><span class="o">-</span> <span class="ss">task: </span><span class="no">PowerShell</span><span class="err">@</span><span class="mi">2</span>
        <span class="ss">displayName: </span><span class="s1">'IaC TFC/TFE'</span>
        <span class="ss">inputs:
          filePath: </span><span class="s1">'$(System.DefaultWorkingDirectory)\TFC.ps1'</span>
          <span class="ss">arguments: </span><span class="o">&gt;</span>
            <span class="o">-</span><span class="no">Org</span>  <span class="s2">"$(org)"</span>
            <span class="o">-</span><span class="no">Server</span> <span class="s2">"$(server)"</span> 
            <span class="o">-</span><span class="n">tf_token</span> <span class="s2">"$(tfc_token)"</span>
            <span class="o">-</span><span class="n">workspace_name</span> <span class="s2">"$(tfc_workspace)"</span>
          <span class="ss">pwsh: </span><span class="kp">true</span>
          <span class="ss">workingDirectory: </span><span class="s1">'$(System.DefaultWorkingDirectory)'</span><span class="sb">```
</span></code></pre></div></div>
<p>The below is the PowerShell Script that interacts with the Terraform Cloud API to carry on the taks on my workspace (already created by another TFC workspace 🐱‍👤).  The script is the very least you will need to make the right calls and deploy successfully the infrastructure is the Terraform Plan is successful. However, it will require refinenment to be more productionalized e.g. Logging and more error handling due to the multiple responses from Terraform Cloud.</p>

<p>The Script goes through the following process:</p>

<ol>
  <li>Create a configuration to upload: this needs to be on a .tar.gz file</li>
  <li>Parse the Terraform Workspace ID: where the config is to be uploaded, planned and applied to the Cloud platform in this case <code class="language-plaintext highlighter-rouge">Azure</code>. The workspace already has the variables required to authenticate via <code class="language-plaintext highlighter-rouge">AzureRM Provider</code></li>
  <li>Create Configuration Version: this <code class="language-plaintext highlighter-rouge">configuration-version</code> is created to associate uploaded content with the workspace. This API call performs two tasks: it creates the new configuration version and it extracts the upload URL to be used in the next step.</li>
  <li>Upload Config: it will upload the config and provided I have setup the <code class="language-plaintext highlighter-rouge">auto-queue-runs</code> = <code class="language-plaintext highlighter-rouge">true</code> it will start a run with a plan</li>
  <li>Terraform Plan: it will run the plan and go through a logic to get to the next step. If plan is successful, the plan output will print out and will trigger an Apply Run. In case there are no changes to be made then it will not execute an <code class="language-plaintext highlighter-rouge">Apply</code> and the task in Azure DevOps will be successful to continue with the Code build task. The plan status will be checked.</li>
  <li>Terraform Apply: it will apply the configuration changes and save the <code class="language-plaintext highlighter-rouge">state file</code> in Terraform Cloud workspace. The Apply status will be checked and after the apply run is finished it will print out the explicit outputs created on screen from the <code class="language-plaintext highlighter-rouge">outputs.tf</code>.</li>
</ol>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">cmdletBinding</span><span class="p">()]</span>
<span class="no">Param</span><span class="p">(</span>
    <span class="p">[</span><span class="no">Parameter</span><span class="p">(</span><span class="no">Mandatory</span> <span class="o">=</span> <span class="vg">$true</span><span class="p">)]</span>
    <span class="p">[</span><span class="n">string</span><span class="p">]</span> <span class="vg">$Org</span><span class="p">,</span>
    <span class="p">[</span><span class="no">Parameter</span><span class="p">(</span><span class="no">Mandatory</span> <span class="o">=</span> <span class="vg">$true</span><span class="p">)]</span>
    <span class="p">[</span><span class="n">string</span><span class="p">]</span> <span class="vg">$Server</span><span class="p">,</span>
    <span class="p">[</span><span class="no">Parameter</span><span class="p">(</span><span class="no">Mandatory</span> <span class="o">=</span> <span class="vg">$true</span><span class="p">)]</span>
    <span class="p">[</span><span class="n">string</span><span class="p">]</span> <span class="vg">$TF_TOKEN</span><span class="p">,</span>
    <span class="p">[</span><span class="no">Parameter</span><span class="p">(</span><span class="no">Mandatory</span> <span class="o">=</span> <span class="vg">$true</span><span class="p">)]</span>
    <span class="p">[</span><span class="n">string</span><span class="p">]</span> <span class="vg">$WORKSPACE_NAME</span>
<span class="p">)</span>

<span class="c1">#Configuration files to upload as .tar.gz. This is required as we are not fetching files from version control directly to the workspace</span>
<span class="vg">$UPLOAD_FILE_NAME</span> <span class="o">=</span> <span class="s2">"content-$((get-date).ToString("</span><span class="n">yyyyMMdd</span><span class="s2">")).tar.gz"</span>
<span class="n">cd</span> <span class="no">Terraform</span>
<span class="n">tar</span> <span class="o">-</span><span class="n">cvzf</span> <span class="vg">$UPLOAD_FILE_NAME</span> <span class="o">.</span><span class="p">\</span><span class="o">*</span><span class="p">.</span><span class="nf">tf</span> 

<span class="vg">$headers</span> <span class="o">=</span> <span class="err">@</span><span class="p">{</span>
    <span class="no">Authorization</span> <span class="o">=</span> <span class="s2">"Bearer $TF_TOKEN"</span>
<span class="p">}</span>

<span class="vg">$body</span> <span class="o">=</span> <span class="err">@</span><span class="s2">"
            {
                "</span><span class="n">data</span><span class="s2">": {
                    "</span><span class="n">type</span><span class="s2">": "</span><span class="n">configuration</span><span class="o">-</span><span class="n">versions</span><span class="s2">",
                    "</span><span class="n">attributes</span><span class="s2">": {
                      "</span><span class="n">auto</span><span class="o">-</span><span class="n">queue</span><span class="o">-</span><span class="n">runs</span><span class="s2">": true
                        }
                  }
            }
"</span><span class="err">@</span>

<span class="vg">$apply_on</span> <span class="o">=</span> <span class="err">@</span><span class="s2">"
              {
            "</span><span class="n">comment</span><span class="s2">": "</span><span class="n">apply</span> <span class="n">via</span>  <span class="no">CeRoCooL</span> <span class="no">API</span><span class="s2">"
              }
"</span><span class="err">@</span>

<span class="c1">#parsing Workspace ID</span>
<span class="vg">$WORKSPACE_ID</span> <span class="o">=</span> <span class="p">(</span><span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="s2">"https://$Server/api/v2/organizations/$Org/workspaces/$WORKSPACE_NAME"</span> <span class="o">-</span><span class="no">Method</span> <span class="no">Get</span> <span class="o">-</span><span class="no">ContentType</span> <span class="s2">"application/vnd.api+json"</span> <span class="o">-</span><span class="no">Headers</span> <span class="vg">$headers</span><span class="p">).</span><span class="nf">data</span><span class="p">.</span><span class="nf">id</span>

<span class="c1">#Create configuration Version</span>
<span class="vg">$UPLOAD_URL</span> <span class="o">=</span> <span class="p">(</span><span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="s2">"https://$Server/api/v2/workspaces/$WORKSPACE_ID/configuration-versions"</span> <span class="o">-</span><span class="no">Method</span> <span class="no">POST</span> <span class="o">-</span><span class="no">ContentType</span> <span class="s2">"application/vnd.api+json"</span> <span class="o">-</span><span class="no">Headers</span> <span class="vg">$headers</span> <span class="o">-</span><span class="no">Body</span> <span class="vg">$body</span><span class="p">).</span><span class="nf">data</span><span class="p">.</span><span class="nf">attributes</span><span class="o">.</span><span class="s2">"upload-url"</span>

<span class="c1">#Upload Configuration File</span>
<span class="vg">$UPLOAD_FILE</span> <span class="o">=</span> <span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="vg">$UPLOAD_URL</span> <span class="o">-</span><span class="no">Method</span> <span class="no">Put</span> <span class="o">-</span><span class="no">ContentType</span> <span class="s2">"application/octet-stream"</span>  <span class="o">-</span><span class="no">InFile</span> <span class="vg">$UPLOAD_FILE_NAME</span>

<span class="vg">$id</span> <span class="o">=</span> <span class="err">@</span><span class="s2">"
        {
    "</span><span class="n">data</span><span class="s2">": {
        "</span><span class="n">attributes</span><span class="s2">": {
            "</span><span class="n">is</span><span class="o">-</span><span class="n">destroy</span><span class="s2">": false
        },
        "</span><span class="n">type</span><span class="s2">": "</span><span class="n">runs</span><span class="s2">",
        "</span><span class="n">relationships</span><span class="s2">": {
            "</span><span class="n">workspace</span><span class="s2">": {
                "</span><span class="n">data</span><span class="s2">": {
                    "</span><span class="n">type</span><span class="s2">": "</span><span class="n">workspaces</span><span class="s2">",
                    "</span><span class="nb">id</span><span class="s2">": "</span><span class="vg">$WORKSPACE_ID</span><span class="s2">"
                }
            }
        }
    }
}
"</span><span class="err">@</span>

<span class="c1">#Parse Run ID </span>
<span class="vg">$RUN_ID</span> <span class="o">=</span> <span class="p">(</span><span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="s2">"https://$Server/api/v2/runs"</span> <span class="o">-</span><span class="no">Method</span> <span class="no">Get</span> <span class="o">-</span><span class="no">ContentType</span> <span class="s2">"application/vnd.api+json"</span> <span class="o">-</span><span class="no">Headers</span> <span class="vg">$headers</span> <span class="o">-</span><span class="no">Body</span> <span class="vg">$id</span><span class="p">).</span><span class="nf">data</span><span class="p">.</span><span class="nf">id</span> <span class="o">|</span> <span class="no">Select</span><span class="o">-</span><span class="no">Object</span> <span class="o">-</span><span class="no">First</span> <span class="mi">1</span>
<span class="vg">$continue</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">while</span> <span class="p">(</span><span class="vg">$continue</span> <span class="o">-</span><span class="n">ne</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">foreach</span> <span class="p">(</span><span class="vg">$RUN</span> <span class="k">in</span> <span class="vg">$RUN_ID</span><span class="p">)</span> <span class="p">{</span>
        <span class="vg">$RESULT</span> <span class="o">=</span> <span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="s2">"https://$Server/api/v2/runs/$RUN"</span> <span class="o">-</span><span class="no">Method</span> <span class="no">Get</span> <span class="o">-</span><span class="no">ContentType</span> <span class="s2">"application/vnd.api+json"</span> <span class="o">-</span><span class="no">Headers</span> <span class="vg">$headers</span> <span class="o">-</span><span class="no">Body</span> <span class="vg">$id</span>
        <span class="vg">$STATUS</span> <span class="o">=</span> <span class="vg">$RESULT</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">attributes</span><span class="p">.</span><span class="nf">status</span>
        <span class="vg">$CONFIRMABLE</span> <span class="o">=</span> <span class="vg">$RESULT</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">attributes</span><span class="p">.</span><span class="nf">actions</span><span class="o">.</span><span class="s2">"is-confirmable"</span>
        <span class="c1">#Verifies plan has succesfully finished</span>
        <span class="k">if</span> <span class="p">(</span><span class="vg">$STATUS</span> <span class="o">-</span><span class="n">eq</span> <span class="s2">"planned"</span> <span class="o">-</span><span class="n">and</span> <span class="vg">$CONFIRMABLE</span> <span class="o">-</span><span class="n">eq</span> <span class="s2">"False"</span><span class="p">)</span> <span class="p">{</span>
            <span class="vg">$PLAN</span> <span class="o">=</span> <span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="p">(</span><span class="s2">"https://$Server/api/v2/runs/$RUN"</span> <span class="o">+</span> <span class="s2">"?include=plan"</span><span class="p">)</span> <span class="o">-</span><span class="no">Method</span> <span class="no">Get</span> <span class="o">-</span><span class="no">ContentType</span> <span class="s2">"application/vnd.api+json"</span> <span class="o">-</span><span class="no">Headers</span> <span class="vg">$headers</span>
            <span class="vg">$PLAN_LOG</span> <span class="o">=</span> <span class="vg">$PLAN</span><span class="p">.</span><span class="nf">included</span><span class="p">.</span><span class="nf">attributes</span><span class="o">.</span><span class="s2">"log-read-url"</span>
            <span class="c1">#print out Plan Log for verification</span>
            <span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="vg">$PLAN_LOG</span>
            <span class="vg">$continue</span> <span class="o">=</span> <span class="mi">0</span>
            <span class="c1">#start Apply Process after succesful Plan</span>
            <span class="vg">$APPLY</span> <span class="o">=</span> <span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="s2">"https://$Server/api/v2/runs/$RUN/actions/apply"</span> <span class="o">-</span><span class="no">Method</span> <span class="no">Post</span> <span class="o">-</span><span class="no">ContentType</span> <span class="s2">"application/vnd.api+json"</span> <span class="o">-</span><span class="no">Headers</span> <span class="vg">$headers</span> <span class="o">-</span><span class="no">Body</span> <span class="vg">$apply_on</span>

            <span class="vg">$RESULT</span> <span class="o">=</span> <span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="p">(</span><span class="s2">"https://$Server/api/v2/runs/$RUN"</span> <span class="o">+</span> <span class="s2">"?include=apply"</span><span class="p">)</span> <span class="o">-</span><span class="no">Method</span> <span class="no">Get</span> <span class="o">-</span><span class="no">ContentType</span> <span class="s2">"application/vnd.api+json"</span> <span class="o">-</span><span class="no">Headers</span> <span class="vg">$headers</span>

            <span class="c1"># Get apply ID</span>
            <span class="vg">$APPLY_ID</span> <span class="o">=</span> <span class="vg">$RESULT</span><span class="p">.</span><span class="nf">included</span><span class="p">.</span><span class="nf">id</span>

            <span class="vg">$continue</span> <span class="o">=</span> <span class="mi">1</span>
            <span class="k">while</span> <span class="p">(</span><span class="vg">$continue</span> <span class="o">-</span><span class="n">ne</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="vg">$RESULT</span> <span class="o">=</span> <span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="s2">"https://$Server/api/v2/applies/$APPLY_ID"</span> <span class="o">-</span><span class="no">Method</span> <span class="no">Get</span> <span class="o">-</span><span class="no">ContentType</span> <span class="s2">"application/vnd.api+json"</span> <span class="o">-</span><span class="no">Headers</span> <span class="vg">$headers</span>
                <span class="vg">$STATUS</span> <span class="o">=</span> <span class="vg">$RESULT</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">attributes</span><span class="p">.</span><span class="nf">status</span>
                <span class="no">Write</span><span class="o">-</span><span class="no">Output</span> <span class="vg">$STATUS</span>
                <span class="k">if</span> <span class="p">(</span><span class="vg">$STATUS</span> <span class="o">-</span><span class="n">eq</span> <span class="s2">"finished"</span><span class="p">)</span> <span class="p">{</span>
                    <span class="no">Write</span><span class="o">-</span><span class="no">Host</span> <span class="s2">"Apply finished"</span>
                    <span class="vg">$APPLY_LOG</span> <span class="o">=</span> <span class="vg">$RESULT</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">attributes</span><span class="o">.</span><span class="s1">'log-read-url'</span>
                    <span class="vg">$STATE_ID</span> <span class="o">=</span> <span class="vg">$RESULT</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">relationships</span><span class="o">.</span><span class="s1">'state-versions'</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">id</span>
                    <span class="vg">$STATE_LOG</span> <span class="o">=</span> <span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="p">(</span><span class="s2">"https://$Server/api/v2/state-versions/$STATE_ID"</span> <span class="o">+</span> <span class="s2">"?include=outputs"</span><span class="p">)</span> <span class="o">-</span><span class="no">Method</span> <span class="no">Get</span> <span class="o">-</span><span class="no">ContentType</span> <span class="s2">"application/vnd.api+json"</span> <span class="o">-</span><span class="no">Headers</span> <span class="vg">$headers</span>
                    <span class="vg">$OUTPUTS</span> <span class="o">=</span> <span class="p">(</span><span class="vg">$STATE_LOG</span><span class="p">.</span><span class="nf">included</span><span class="p">)</span><span class="o">.</span><span class="no">Count</span>
                    <span class="c1">#Get all Outputs on screen </span>
                    <span class="k">for</span> <span class="p">(</span><span class="vg">$OUTPUT</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="vg">$OUTPUT</span> <span class="o">-</span><span class="n">lt</span> <span class="vg">$OUTPUTS</span><span class="p">;</span> <span class="vg">$OUTPUT</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
                        <span class="vg">$STATE_LOG</span><span class="p">.</span><span class="nf">included</span><span class="p">[</span><span class="vg">$OUTPUT</span><span class="p">].</span><span class="nf">attributes</span>
                    <span class="p">}</span>
                    <span class="vg">$continue</span> <span class="o">=</span> <span class="mi">0</span>
                <span class="p">}</span>
                <span class="n">elseif</span> <span class="p">(</span><span class="vg">$STATUS</span> <span class="o">-</span><span class="n">eq</span> <span class="s2">"errored"</span><span class="p">)</span> <span class="p">{</span>
                    <span class="no">Write</span><span class="o">-</span><span class="no">Host</span> <span class="s2">"Terraform Apply errored"</span>
                    <span class="vg">$APPLY_LOG</span> <span class="o">=</span> <span class="vg">$RESULT</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">attributes</span><span class="o">.</span><span class="s1">'log-read-url'</span>
                    <span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="vg">$APPLY_LOG</span>
                    <span class="vg">$continue</span> <span class="o">=</span> <span class="mi">0</span>
                <span class="p">}</span>
                <span class="n">elseif</span> <span class="p">(</span><span class="vg">$STATUS</span> <span class="o">-</span><span class="n">eq</span> <span class="s2">"canceled"</span><span class="p">)</span> <span class="p">{</span>
                    <span class="no">Write</span><span class="o">-</span><span class="no">Host</span> <span class="s2">"Terraform Apply canceled"</span>
                    <span class="vg">$APPLY_LOG</span> <span class="o">=</span> <span class="vg">$RESULT</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">attributes</span><span class="o">.</span><span class="s1">'log-read-url'</span>
                    <span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="vg">$APPLY_LOG</span>
                    <span class="vg">$continue</span> <span class="o">=</span> <span class="mi">0</span>
                <span class="p">}</span>
                <span class="k">else</span> <span class="p">{</span>
                    <span class="no">Write</span><span class="o">-</span><span class="no">Host</span> <span class="s2">"Terraform Apply in progress"</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="vg">$STATUS</span> <span class="o">-</span><span class="n">eq</span> <span class="s2">"planned_and_finished"</span><span class="p">)</span> <span class="p">{</span>
            <span class="vg">$PLAN</span> <span class="o">=</span> <span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="p">(</span><span class="s2">"https://$Server/api/v2/runs/$RUN"</span> <span class="o">+</span> <span class="s2">"?include=plan"</span><span class="p">)</span> <span class="o">-</span><span class="no">Method</span> <span class="no">Get</span> <span class="o">-</span><span class="no">ContentType</span> <span class="s2">"application/vnd.api+json"</span> <span class="o">-</span><span class="no">Headers</span> <span class="vg">$headers</span>
            <span class="vg">$PLAN_LOG</span> <span class="o">=</span> <span class="vg">$PLAN</span><span class="p">.</span><span class="nf">included</span><span class="p">.</span><span class="nf">attributes</span><span class="o">.</span><span class="s2">"log-read-url"</span>
            <span class="no">Invoke</span><span class="o">-</span><span class="no">RestMethod</span>  <span class="o">-</span><span class="no">Uri</span> <span class="vg">$PLAN_LOG</span>
            <span class="vg">$continue</span> <span class="o">=</span> <span class="mi">0</span>
            <span class="k">break</span>
        <span class="p">}</span>
        <span class="k">else</span> <span class="p">{</span> <span class="no">Write</span><span class="o">-</span><span class="no">Host</span> <span class="s2">"Terraform Plan in Progress"</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="infrastructure-as-code-deployment-in-action">Infrastructure as Code deployment in Action</h3>
<p>The below is a quick demo of the Azure DevOps and Terraform Cloud interaction triggered via API calls to successfully deploy the Infrastructure.</p>

<p><img src="/assets\images\Blog\2021-07-18\TFC_AZDO.gif" alt="image-center" class="align-center" /></p>

<h2 id="application-code-build">Application Code build</h2>
<p>The below describes the minimal steps required to build and generate an artefact to be deployed in the Azure Web App staging slot.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ss">stage: </span><span class="n">build</span>
  <span class="ss">displayName: </span><span class="no">App</span> <span class="no">Build</span> <span class="n">and</span> <span class="no">Staging</span> <span class="no">Deployment</span>
  <span class="ss">jobs:
  </span><span class="o">-</span> <span class="ss">job: </span><span class="no">Build</span>
    <span class="ss">steps:
      </span><span class="o">-</span> <span class="ss">task: </span><span class="no">NodeTool</span><span class="err">@</span><span class="mi">0</span>
        <span class="ss">displayName: </span><span class="s1">'Node.js version'</span>
        <span class="ss">inputs:
          versionSpec: </span><span class="mi">12</span><span class="p">.</span><span class="nf">x</span>

      <span class="o">-</span> <span class="ss">task: </span><span class="no">Npm</span><span class="err">@</span><span class="mi">0</span>
        <span class="ss">displayName: </span><span class="s1">'npm install'</span>
        <span class="ss">inputs:
          arguments: </span><span class="s1">'--force'</span>

      <span class="o">-</span> <span class="ss">task: </span><span class="no">Npm</span><span class="err">@</span><span class="mi">1</span>
        <span class="ss">displayName: </span><span class="s1">'npm build'</span>
        <span class="ss">inputs:
          command: </span><span class="n">custom</span>
          <span class="ss">verbose: </span><span class="kp">false</span>
          <span class="ss">customCommand: </span><span class="s1">'run build'</span>

      <span class="o">-</span> <span class="ss">task: </span><span class="no">ArchiveFiles</span><span class="err">@</span><span class="mi">1</span>
        <span class="ss">displayName: </span><span class="s1">'Archive files '</span>
        <span class="ss">inputs:
          rootFolder: </span><span class="kp">public</span>

      <span class="o">-</span> <span class="ss">task: </span><span class="no">CopyFiles</span><span class="err">@</span><span class="mi">2</span>
        <span class="ss">displayName: </span><span class="s1">'Copy Files'</span>
        <span class="ss">inputs:
          </span><span class="no">SourceFolder</span><span class="p">:</span> <span class="s1">'$(Build.ArtifactStagingDirectory)'</span>
          <span class="no">Contents</span><span class="p">:</span> <span class="s1">'$(Build.BuildId).zip'</span>
          <span class="no">TargetFolder</span><span class="p">:</span> <span class="s1">'$(Build.ArtifactStagingDirectory)\ArtifactsToBePublished'</span>

      <span class="o">-</span> <span class="ss">task: </span><span class="no">PublishBuildArtifacts</span><span class="err">@</span><span class="mi">1</span>
        <span class="ss">displayName: </span><span class="s1">'Publish Artifact: drop'</span>
        <span class="ss">inputs:
          </span><span class="no">PathtoPublish</span><span class="p">:</span> <span class="s1">'$(Build.ArtifactStagingDirectory)\ArtifactsToBePublished'</span>
          <span class="ss">artifact: </span><span class="no">Webapp</span>
</code></pre></div></div>
<h2 id="staging-slot-deployment">Staging slot deployment</h2>
<p>The below describes the task required to deploy the artefact generated from the step before to be deployed in the Azure Web App staging slot.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="o">-</span> <span class="ss">task: </span><span class="no">AzureRmWebAppDeployment</span><span class="err">@</span><span class="mi">4</span>
        <span class="ss">displayName: </span><span class="s1">'Staging deployment'</span>
        <span class="ss">inputs:
          azureSubscription: </span><span class="err">$</span><span class="p">(</span><span class="nb">sub</span><span class="p">)</span>
          <span class="ss">appType: </span><span class="n">webAppLinux</span>
          <span class="no">WebAppName</span><span class="p">:</span> <span class="err">$</span><span class="p">(</span><span class="no">WebAppName</span><span class="p">)</span>
          <span class="ss">deployToSlotOrASE: </span><span class="kp">true</span>
          <span class="no">ResourceGroupName</span><span class="p">:</span> <span class="s1">'$(RG)'</span>
          <span class="no">SlotName</span><span class="p">:</span> <span class="n">staging</span>
          <span class="ss">package: </span><span class="err">$</span><span class="p">(</span><span class="no">Build</span><span class="o">.</span><span class="no">ArtifactStagingDirectory</span><span class="p">)\</span><span class="no">ArtifactsToBePublished</span><span class="p">\</span><span class="o">*</span><span class="p">.</span><span class="nf">zip</span> 
</code></pre></div></div>
<h2 id="web-app-deployment-to-production">Web App Deployment to Production</h2>
<p>In this stage, I have two different tasks to ensure there is a manual approval before the code in the staging slot is swapped against the production one. I could potentially just deployed the code directly to the Prod slot. However, I find the swapping feature a nice way to ensure zero-downtime deployments and a way to validate and/or test new features before pushing it into Production.</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">-</span> <span class="ss">stage: </span><span class="n">production</span>
  <span class="ss">displayName: </span><span class="no">App</span> <span class="no">Deployment</span> <span class="no">Production</span>
  <span class="ss">jobs:
  </span><span class="o">-</span> <span class="ss">job: </span><span class="n">waitForValidation</span>
    <span class="ss">displayName: </span><span class="no">Approval</span>   
    <span class="ss">pool: </span><span class="n">server</span>    
    <span class="ss">timeoutInMinutes: </span><span class="mi">4320</span> <span class="c1"># job times out in 3 days</span>
    <span class="ss">steps:   
    </span><span class="o">-</span> <span class="ss">task: </span><span class="no">ManualValidation</span><span class="err">@</span><span class="mi">0</span>
      <span class="ss">timeoutInMinutes: </span><span class="mi">1440</span> <span class="c1"># task times out in 1 day</span>
      <span class="ss">inputs:
        notifyUsers: </span><span class="o">|</span>
          <span class="n">email</span><span class="vi">@gmail</span><span class="p">.</span><span class="nf">com</span>
        <span class="ss">instructions: </span><span class="s1">'Please validate the build configuration and resume'</span>
        <span class="ss">onTimeout: </span><span class="s1">'resume'</span> 
  <span class="o">-</span> <span class="ss">job: </span><span class="no">Production_Release</span>
    <span class="ss">steps:
      </span><span class="o">-</span> <span class="ss">task: </span><span class="no">AzureAppServiceManage</span><span class="err">@</span><span class="mi">0</span>
        <span class="ss">displayName: </span><span class="s1">'Production deployment'</span>
        <span class="ss">inputs:
          azureSubscription: </span><span class="err">$</span><span class="p">(</span><span class="nb">sub</span><span class="p">)</span>
          <span class="no">WebAppName</span><span class="p">:</span> <span class="err">$</span><span class="p">(</span><span class="no">WebAppName</span><span class="p">)</span>
          <span class="no">ResourceGroupName</span><span class="p">:</span> <span class="s1">'$(RG)'</span>
          <span class="no">SourceSlot</span><span class="p">:</span> <span class="n">staging</span>
</code></pre></div></div>
<h2 id="summary">Summary</h2>

<p>My focus on this post was to demo and show a way using PowerShell to interact with Terraform Cloud and/or Terraform Enterprise using the API-driven Run Workflow in an Azure DevOps pipeline. However, this will work with any CI/CD tool you may be working with.</p>

<p>I did have some fun and removed some rust from my Azure DevOps days. If you find yourselves already invested in TFC/TFE this is definitely a way that will allow you to have more flexibility while taking advantage of the features offered by enterprise products.</p>

<p>I do hope this helps someone and that you find it informative,, so please let me know as constructive feedback is always important🕵️‍♂️,, That’s it for now,,, Hasta la vista🐱‍🏍!!!</p>

<p><strong>🚴‍♂️ If you enjoyed this blog, you can empower me with some caffeine to continue working in new content 🚴‍♂️.</strong></p>

<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>]]></content><author><name>John Alfaro</name></author><category term="Blog" /><category term="Azure Web App" /><category term="PowerShell" /><category term="Azure DevOps" /><category term="Terraform Cloud" /><summary type="html"><![CDATA[This article is demonstrating a deployment using Azure DevOps and Terraform Cloud API-driven Run Workflow with a pinch of PowerShell magic for an Azure Web App]]></summary></entry><entry><title type="html">Deploying Private AKS using Bicep, because ARM is not strong enough :🦾!!!</title><link href="https:/blog.johnalfaro.com/blog/Bicep" rel="alternate" type="text/html" title="Deploying Private AKS using Bicep, because ARM is not strong enough :🦾!!!" /><published>2021-04-04T00:00:00+11:00</published><updated>2021-04-04T00:00:00+11:00</updated><id>https:/blog.johnalfaro.com/blog/Bicep</id><content type="html" xml:base="https:/blog.johnalfaro.com/blog/Bicep"><![CDATA[<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>

<p><a href="https://github.com/Azure/bicep">Project Bicep</a> is taking ARM to the next level and it is making lots of good noise in the IaC  world. I have been using terraform for a while so I decided to check it out and see how it compares to HCL language to deploy IaC in Azure Platform and also to see how easy it is to author these kind of templates.  :astonished:
Given that now Bicep support the creation of modules it makes it even more appealing, still early stages but looking forward to feature parity to terraform</p>

<h2 id="environment-preparation-for-bicep">Environment preparation for Bicep</h2>

<p>What do I need to get started?</p>

<ol>
  <li>I am using WSL2 on Win10 for my environment so I have updated my Azure CLI version to the latest, as it is required to be on v2.20 or later. Then I installed Bicep compiler running the command <code class="language-plaintext highlighter-rouge">az bicep install</code>.</li>
  <li>I installed the <a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep">Bicep extension</a> which definitely makes life so much easier as the intellisense is great and it makes authoring quite enjoyable</li>
  <li>I did rely on the <a href="https://docs.microsoft.com/en-us/azure/templates/">Azure ARM Template</a> documentation in order to check the values to use in the Bicep template regarding resource types similar to the <a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest">Azure RM provider</a> documentation in terraform. I had a few challenges here as Terraform API calls may use different variable names to interact with the Azure API’s.</li>
</ol>

<h2 id="aks-the-terraform-way">AKS the terraform way</h2>

<p>My terraform deployment uses modules stored in my Private Terraform Cloud Module repository. In that case, the structure of the deployment files looks as per below. The module structure in terraform is per below the right hand side. For instance, my deployment <code class="language-plaintext highlighter-rouge">AKS Deployment</code> will call all the approved modules to be consumed and deploy the whole solution.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">AKS</span> <span class="no">Deployment</span>
        <span class="o">|</span><span class="n">_main</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span>    <span class="o">|</span><span class="n">_</span> <span class="no">AKS</span> <span class="no">Module</span>
        <span class="o">|</span>    <span class="o">|</span>   <span class="o">|</span><span class="n">_</span> <span class="n">main</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span>    <span class="o">|</span>   <span class="o">|</span><span class="n">_</span> <span class="n">variables</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span>    <span class="o">|</span>   <span class="o">|</span><span class="n">_</span> <span class="n">outputs</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span>    <span class="o">|</span>
        <span class="o">|</span>    <span class="o">|</span><span class="n">_</span> <span class="no">VNet</span> <span class="no">Module</span>
        <span class="o">|</span>    <span class="o">|</span>   <span class="o">|</span><span class="n">_</span> <span class="n">main</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span>    <span class="o">|</span>   <span class="o">|</span><span class="n">_</span> <span class="n">variables</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span>    <span class="o">|</span>   <span class="o">|</span><span class="n">_</span> <span class="n">outputs</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span>    <span class="o">|</span>
        <span class="o">|</span>    <span class="o">|</span><span class="n">_</span> <span class="no">Subnet</span> <span class="no">Module</span>
        <span class="o">|</span>    <span class="o">|</span>   <span class="o">|</span><span class="n">_</span> <span class="n">main</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span>    <span class="o">|</span>   <span class="o">|</span><span class="n">_</span> <span class="n">variables</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span>    <span class="o">|</span>   <span class="o">|</span><span class="n">_</span> <span class="n">outputs</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span>    <span class="o">|</span>
        <span class="o">|</span>    <span class="o">|</span><span class="n">_</span> <span class="no">Route</span> <span class="no">Table</span> <span class="no">Module</span>
        <span class="o">|</span>        <span class="o">|</span><span class="n">_</span> <span class="n">main</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span>        <span class="o">|</span><span class="n">_</span> <span class="n">variables</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span>        <span class="o">|</span><span class="n">_</span> <span class="n">outputs</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span>
        <span class="o">|</span><span class="n">_variables</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span><span class="n">_outputs</span><span class="p">.</span><span class="nf">tf</span>
        <span class="o">|</span><span class="n">_providers</span><span class="p">.</span><span class="nf">tf</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">main.tf</code> file of the AKS module looks like below with all the values to be provided by populating the variables. 👇</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">resource</span> <span class="dl">"</span><span class="s2">azurerm_kubernetes_cluster</span><span class="dl">"</span> <span class="dl">"</span><span class="s2">test</span><span class="dl">"</span> <span class="p">{</span>

  <span class="nx">name</span>                      <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">name</span>
  <span class="nx">resource_group_name</span>       <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">resource_group</span><span class="p">.</span><span class="nx">name</span>
  <span class="nx">location</span>                  <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">resource_group</span><span class="p">.</span><span class="nx">location</span>
  <span class="nx">dns_prefix</span>                <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">dns_prefix</span>
  <span class="nx">kubernetes_version</span>        <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">kubernetes_version</span>
  <span class="nx">private_cluster_enabled</span>   <span class="o">=</span> <span class="kc">true</span>

  <span class="nx">default_node_pool</span> <span class="p">{</span>
    <span class="nx">name</span>                 <span class="o">=</span> <span class="dl">"</span><span class="s2">default</span><span class="dl">"</span>
    <span class="nx">orchestrator_version</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">kubernetes_version</span>
    <span class="nx">node_count</span>           <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">node_count</span>
    <span class="nx">vm_size</span>              <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">node_size</span>
    <span class="nx">os_disk_size_gb</span>      <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">os_disk_size_gb</span>
    <span class="nx">vnet_subnet_id</span>       <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">node_vnet_subnet_id</span>
    <span class="nx">max_pods</span>             <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">max_pods</span>
    <span class="nx">enable_auto_scaling</span>  <span class="o">=</span> <span class="kc">true</span>
    <span class="nx">min_count</span>            <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">auto_scaling_min_count</span>
    <span class="nx">max_count</span>            <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">auto_scaling_max_count</span>
    <span class="nx">availability_zones</span>   <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">availability_zones</span>
  <span class="p">}</span>

  <span class="nx">addon_profile</span> <span class="p">{</span>

    <span class="nx">azure_policy</span> <span class="p">{</span>
      <span class="nx">enabled</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">role_based_access_control</span> <span class="p">{</span>
    <span class="nx">enabled</span> <span class="o">=</span> <span class="kc">true</span>

    <span class="nx">azure_active_directory</span> <span class="p">{</span>
      <span class="nx">managed</span> <span class="o">=</span> <span class="kc">true</span>

      <span class="nx">admin_group_object_ids</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">aad_group_id</span>
    <span class="p">}</span>
  <span class="p">}</span>

<span class="nx">identity</span> <span class="p">{</span>
    <span class="nx">type</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">SystemAssigned</span><span class="dl">"</span>
  <span class="p">}</span>

  <span class="nx">network_profile</span> <span class="p">{</span>
    <span class="nx">network_plugin</span>     <span class="o">=</span> <span class="dl">"</span><span class="s2">azure</span><span class="dl">"</span>
    <span class="nx">network_policy</span>     <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">network_policy</span>
    <span class="nx">load_balancer_sku</span>  <span class="o">=</span> <span class="dl">"</span><span class="s2">standard</span><span class="dl">"</span>
    <span class="nx">outbound_type</span>      <span class="o">=</span> <span class="dl">"</span><span class="s2">userDefinedRouting</span><span class="dl">"</span>
    <span class="nx">service_cidr</span>       <span class="o">=</span> <span class="dl">"</span><span class="s2">192.168.254.0/24</span><span class="dl">"</span>
    <span class="nx">dns_service_ip</span>     <span class="o">=</span> <span class="dl">"</span><span class="s2">192.168.254.10</span><span class="dl">"</span>
    <span class="nx">docker_bridge_cidr</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">172.22.0.1/16</span><span class="dl">"</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The deployment uses the <code class="language-plaintext highlighter-rouge">provider.tf</code> for the authentication to the Azure Platform for the deployment of the resources as such with a block similar to the one below when using terraform open source or you will have a service principal already assigned in TFC/TFE on the workspace for the deployment. Please refer to <a href="https://blog.johnalfaro.com/blog/TerraformCloud/#terraform-configuration">Terraform Configuration</a></p>

<!-- ![image-center](/assets/images/Blog/2020-09-03/hub-spoke.jpg){: .align-center} -->
<h2 id="aks-the-bicep-way-">AKS the Bicep way 🦾</h2>
<p>Azure Bicep did not disappoint and by using, the VSCode extension intellisense I was able to write the module in a few minutes in a similar fashion to my Terraform Module, still not as robust as terraform but I guess it is early stages for Bicep. The file structure is quite similar except here at this stage I cannot have my variables/parameters and outputs on separate files. In addition, I do not need the provider file, as this is ARM after all so the context used (user/SPN/identity) will be used for the deployment.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">AKS</span> <span class="no">Deployment</span>
        <span class="o">|</span><span class="n">_main</span><span class="p">.</span><span class="nf">bicep</span>
            <span class="o">|</span><span class="n">_</span> <span class="no">AKS</span> <span class="no">Module</span>
            <span class="o">|</span>   <span class="o">|</span><span class="n">_</span> <span class="n">aks</span><span class="p">.</span><span class="nf">bicep</span>
            <span class="o">|</span>
            <span class="o">|</span><span class="n">_</span> <span class="no">Network</span> <span class="no">Module</span>
            <span class="o">|</span>   <span class="o">|</span><span class="n">_</span> <span class="n">vnet</span><span class="p">.</span><span class="nf">bicep</span>
            <span class="o">|</span>
            <span class="o">|</span><span class="n">_</span> <span class="no">Route</span> <span class="no">Table</span> <span class="no">Module</span>
                <span class="o">|</span><span class="n">_</span> <span class="n">routetable</span><span class="p">.</span><span class="nf">bicep</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">aks.bicep</code> file of the AKS module looks like below with all the values to be provided by populating the variables. 👇</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">param</span> <span class="n">location</span> <span class="n">string</span> <span class="o">=</span> <span class="n">resourceGroup</span><span class="p">().</span><span class="nf">location</span>
<span class="n">param</span> <span class="no">AKSName</span> <span class="n">string</span>
<span class="n">param</span> <span class="n">subnet_id</span> <span class="n">string</span> 
<span class="n">param</span> <span class="n">group_id</span> <span class="n">string</span>
<span class="n">param</span> <span class="n">dns_prefix</span> <span class="n">string</span>
<span class="n">param</span> <span class="n">kubernetes_version</span> <span class="n">string</span>
<span class="n">param</span> <span class="n">node_count</span> <span class="n">int</span>
<span class="n">param</span> <span class="n">auto_scaling_min_count</span> <span class="n">int</span>
<span class="n">param</span> <span class="n">auto_scaling_max_count</span> <span class="n">int</span>
<span class="n">param</span> <span class="n">max_pods</span> <span class="n">int</span>
<span class="n">param</span> <span class="n">os_disk_size_gb</span> <span class="n">int</span>
<span class="n">param</span> <span class="n">node_size</span> <span class="n">string</span>

<span class="n">resource</span> <span class="n">aks</span> <span class="s1">'Microsoft.ContainerService/managedClusters@2021-02-01'</span> <span class="o">=</span> <span class="p">{</span>
  <span class="ss">name: </span><span class="no">AKSName</span>
  <span class="ss">location: </span><span class="n">location</span>
  <span class="ss">properties: </span><span class="p">{</span>
    <span class="ss">kubernetesVersion: </span><span class="n">kubernetes_version</span> 
    <span class="ss">enableRBAC: </span><span class="kp">true</span>
    <span class="ss">dnsPrefix: </span><span class="n">dns_prefix</span>
    <span class="ss">aadProfile: </span><span class="p">{</span>
      <span class="ss">managed: </span><span class="kp">true</span>
      <span class="ss">adminGroupObjectIDs: </span><span class="p">[</span>
        <span class="n">group_id</span>
      <span class="p">]</span>
    <span class="p">}</span>
    <span class="ss">networkProfile: </span><span class="p">{</span>
      <span class="ss">networkPlugin: </span><span class="s1">'azure'</span>
      <span class="ss">networkPolicy: </span><span class="s1">'azure'</span>
      <span class="ss">serviceCidr: </span><span class="s1">'192.168.254.0/24'</span>
      <span class="ss">dnsServiceIP: </span><span class="s1">'192.168.254.10'</span>
      <span class="ss">dockerBridgeCidr: </span><span class="s1">'172.22.0.1/16'</span>
      <span class="ss">outboundType: </span><span class="s1">'userDefinedRouting'</span>
    <span class="p">}</span>
    <span class="ss">apiServerAccessProfile: </span><span class="p">{</span>
      <span class="ss">enablePrivateCluster: </span><span class="kp">true</span>
    <span class="p">}</span>
    <span class="ss">addonProfiles: </span><span class="p">{</span>
      <span class="ss">azurepolicy: </span><span class="p">{</span>
        <span class="ss">enabled: </span><span class="kp">false</span>
      <span class="p">}</span>
      <span class="ss">httpApplicationRouting: </span><span class="p">{</span>
        <span class="ss">enabled: </span><span class="kp">false</span>
      <span class="p">}</span>
    <span class="p">}</span>
    <span class="ss">agentPoolProfiles: </span><span class="p">[</span>
      <span class="p">{</span>
        <span class="ss">name: </span><span class="s1">'agentpool'</span>
        <span class="n">mode</span><span class="ss">:'System'</span>
        <span class="ss">osDiskSizeGB: </span><span class="n">os_disk_size_gb</span>
        <span class="ss">count: </span><span class="n">node_count</span>
        <span class="ss">vmSize: </span><span class="n">node_size</span>
        <span class="ss">osType: </span><span class="s1">'Linux'</span>
        <span class="ss">type: </span><span class="s1">'VirtualMachineScaleSets'</span>
        <span class="ss">maxPods: </span><span class="n">max_pods</span>
        <span class="ss">vnetSubnetID: </span><span class="n">subnet_id</span>
        <span class="ss">enableAutoScaling: </span><span class="kp">true</span>
        <span class="ss">minCount: </span><span class="n">auto_scaling_min_count</span>
        <span class="ss">maxCount: </span><span class="n">auto_scaling_max_count</span>
        <span class="ss">orchestratorVersion: </span><span class="n">kubernetes_version</span>
        <span class="ss">availabilityZones: </span><span class="p">[</span>
          <span class="s1">'1'</span>
          <span class="s1">'2'</span>
          <span class="s1">'3'</span>
        <span class="p">]</span>
      <span class="p">}</span>
    <span class="p">]</span>
  <span class="p">}</span>
  <span class="ss">identity: </span><span class="p">{</span>
    <span class="ss">type: </span><span class="s1">'SystemAssigned'</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="n">output</span> <span class="n">object</span> <span class="n">object</span> <span class="o">=</span> <span class="n">aks</span>
<span class="n">output</span> <span class="n">aks_id</span> <span class="n">string</span> <span class="o">=</span> <span class="n">aks</span><span class="p">.</span><span class="nf">properties</span><span class="p">.</span><span class="nf">privateFQDN</span>
<span class="n">output</span> <span class="n">aks_identity</span> <span class="n">object</span> <span class="o">=</span> <span class="n">aks</span><span class="p">.</span><span class="nf">properties</span><span class="p">.</span><span class="nf">identityProfile</span>
</code></pre></div></div>
<p>Definitely much nicer and easier to digest than an ARM in JSON format and quite similar to the terraform language layout at the end of the day same API calls 💪.</p>
<h2 id="module-structure-using-bicep">Module Structure using Bicep</h2>
<p>Modules are called using the following syntax quite similar to terraform module blocks and supports <code class="language-plaintext highlighter-rouge">depends on</code> which was something introduced in terraform world from v13. However, versioning is not supported at this time as well as <code class="language-plaintext highlighter-rouge">&lt;module_path&gt;</code> it’s only local not providing enough flexibility and governance around module creation for reusability.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">module</span> <span class="o">&lt;</span><span class="nb">name</span><span class="o">&gt;</span> <span class="s1">'&lt;module_path&gt;'</span> <span class="p">{</span>
  <span class="ss">name: </span><span class="o">&lt;</span><span class="n">required</span> <span class="nb">name</span> <span class="n">of</span> <span class="n">the</span> <span class="n">nested</span> <span class="n">deployment</span><span class="o">&gt;</span>
  <span class="ss">scope: </span><span class="o">&lt;</span><span class="n">rg</span><span class="o">/</span><span class="n">subscription</span><span class="o">&gt;</span>
  <span class="sr">//</span> <span class="no">Input</span> <span class="no">Parameters</span>
  <span class="o">&lt;</span><span class="n">parameter</span><span class="o">-</span><span class="nb">name</span><span class="o">&gt;</span><span class="p">:</span> <span class="o">&lt;</span><span class="n">parameter</span><span class="o">-</span><span class="n">value</span><span class="o">&gt;</span>
  <span class="n">dependsOn</span><span class="p">:[</span>
    <span class="o">&lt;</span><span class="n">module</span><span class="o">/</span><span class="n">resource</span><span class="o">&gt;</span>
  <span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="solution-deployment-bicep-way">Solution Deployment Bicep Way</h2>
<p>In order to deploy the whole solution. I setup a <code class="language-plaintext highlighter-rouge">main.bicep</code> which makes the calls to the modules for the deployment based on variables and parameters required for each of them. The file will look like below. Given I am deploying the Resource Group to logically group my resources my deployment requires to be scoped to my <code class="language-plaintext highlighter-rouge">subscription</code></p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">targetScope</span> <span class="o">=</span> <span class="s1">'subscription'</span>

<span class="n">param</span> <span class="n">location</span> <span class="n">string</span> <span class="o">=</span> <span class="s1">'australia east'</span>

<span class="n">var</span> <span class="no">RgName</span>          <span class="o">=</span> <span class="s1">'aks-rg-test'</span>
<span class="n">var</span> <span class="no">AKSName</span>         <span class="o">=</span> <span class="s1">'aks-development'</span>
<span class="n">var</span> <span class="no">AKSvNetName</span>     <span class="o">=</span> <span class="s1">'aks-vnet'</span>
<span class="n">var</span> <span class="no">AKSsubnetName</span>   <span class="o">=</span> <span class="s1">'aks-subnet'</span>
<span class="n">var</span> <span class="no">AKSsubnetPrefix</span> <span class="o">=</span> <span class="s1">'10.0.3.0/25'</span>

<span class="sr">//</span><span class="no">Resource</span> <span class="n">group</span>
<span class="n">resource</span> <span class="n">rg</span> <span class="s1">'Microsoft.Resources/resourceGroups@2020-06-01'</span> <span class="o">=</span> <span class="p">{</span>
  <span class="ss">name: </span><span class="no">RgName</span>
  <span class="ss">location: </span><span class="n">location</span>
<span class="p">}</span>

<span class="k">module</span> <span class="nn">vnet</span> <span class="s1">'./vnet.bicep'</span> <span class="o">=</span> <span class="p">{</span>
  <span class="ss">name: </span><span class="no">AKSvNetName</span>
  <span class="ss">scope: </span><span class="n">resourceGroup</span><span class="p">(</span><span class="n">rg</span><span class="p">.</span><span class="nf">name</span><span class="p">)</span>
  <span class="ss">params: </span><span class="p">{</span>
    <span class="ss">location: </span><span class="n">location</span>
    <span class="ss">vNetName: </span><span class="no">AKSvNetName</span>
    <span class="ss">address_space: </span><span class="s1">'10.0.3.0/24'</span>
    <span class="no">SubnetName</span><span class="p">:</span> <span class="no">AKSsubnetName</span>
    <span class="ss">subnetPrefix: </span><span class="no">AKSsubnetPrefix</span>
  <span class="p">}</span>
  <span class="ss">dependsOn: </span><span class="p">[</span>
    <span class="n">rg</span>
  <span class="p">]</span>
<span class="p">}</span>

<span class="k">module</span> <span class="nn">aks</span> <span class="s1">'./aks.bicep'</span> <span class="o">=</span> <span class="p">{</span>
  <span class="ss">name: </span><span class="no">AKSName</span>
  <span class="ss">scope: </span><span class="n">resourceGroup</span><span class="p">(</span><span class="n">rg</span><span class="p">.</span><span class="nf">name</span><span class="p">)</span>
  <span class="ss">params: </span><span class="p">{</span>
    <span class="ss">location: </span><span class="n">location</span>
    <span class="ss">subnet_id: </span><span class="n">vnet</span><span class="p">.</span><span class="nf">outputs</span><span class="p">.</span><span class="nf">subnetid</span>
    <span class="ss">group_id: </span><span class="s1">'bad1b814-cb6e-4027-afd2-ee0d27aef0e1'</span>
    <span class="no">AKSName</span><span class="p">:</span> <span class="s1">'aks-dev'</span>
    <span class="ss">dns_prefix: </span><span class="s1">'dev'</span>
    <span class="ss">kubernetes_version: </span><span class="s1">'1.18.14'</span>
    <span class="ss">node_count: </span><span class="mi">1</span>
    <span class="ss">auto_scaling_min_count: </span><span class="mi">1</span>
    <span class="ss">auto_scaling_max_count: </span><span class="mi">3</span>
    <span class="ss">max_pods: </span><span class="mi">50</span>
    <span class="ss">os_disk_size_gb: </span><span class="mi">64</span>
    <span class="ss">node_size: </span><span class="s1">'Standard_D2_v3'</span>
  <span class="p">}</span>
  <span class="ss">dependsOn: </span><span class="p">[</span>
    <span class="n">vnet</span>
  <span class="p">]</span>
<span class="p">}</span>

<span class="n">output</span> <span class="n">vnetid</span> <span class="n">string</span>    <span class="o">=</span> <span class="n">vnet</span><span class="p">.</span><span class="nf">outputs</span><span class="p">.</span><span class="nf">vnet_id</span>
<span class="n">output</span> <span class="n">aksid</span> <span class="n">string</span>     <span class="o">=</span> <span class="n">aks</span><span class="p">.</span><span class="nf">outputs</span><span class="p">.</span><span class="nf">aks_id</span>
<span class="n">output</span> <span class="n">aks_iden</span> <span class="n">object</span>  <span class="o">=</span> <span class="n">aks</span><span class="p">.</span><span class="nf">outputs</span><span class="p">.</span><span class="nf">aks_identity</span>
</code></pre></div></div>
<p>For the deployment I used Azure CLI and the deployment was just by running the below command. Where <code class="language-plaintext highlighter-rouge">c</code> short name for <code class="language-plaintext highlighter-rouge">--confirm-with-what-if</code> gives me a sense of a dry run similar to our <code class="language-plaintext highlighter-rouge">terraform plan</code>. We can get a JSON representation of a terraform plan like but I guess it will be a bit harder to inspect.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">az</span> <span class="n">deployment</span> <span class="nb">sub</span> <span class="n">create</span> <span class="o">-</span><span class="n">c</span> <span class="o">--</span><span class="nb">name</span> <span class="n">aksbicep</span> <span class="o">--</span><span class="n">template</span><span class="o">-</span><span class="n">file</span> <span class="n">main</span><span class="p">.</span><span class="nf">bicep</span> <span class="o">--</span><span class="n">location</span> <span class="s1">'australiaeast'</span>
</code></pre></div></div>
<p>You can also have JSON representation for the changes by using the command below and you can potentially pipe the results so you can programmatically inspect those changes</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">az</span> <span class="n">deployment</span> <span class="nb">sub</span> <span class="n">what</span><span class="o">-</span><span class="k">if</span> <span class="o">--</span><span class="n">no</span><span class="o">-</span><span class="n">pretty</span><span class="o">-</span><span class="nb">print</span> <span class="o">--</span><span class="nb">name</span> <span class="n">aksbicep</span> <span class="o">--</span><span class="n">template</span><span class="o">-</span><span class="n">file</span> <span class="n">deployment</span><span class="p">.</span><span class="nf">bicep</span> <span class="o">--</span><span class="n">location</span> <span class="s1">'australiaeast'</span>
</code></pre></div></div>
<p><img src="/assets/images/Blog/2021-04-02/module.jpg" alt="image-center" class="align-center" /></p>

<p class="notice--warning"><strong>Important</strong> I run this exercise using my account context with currently has a contributor role assignment. In enterprise scenarios, you will have a more comprehensive RBAC controls and potentially using CI/CD tools that will run under Service Principals or Manage Identities.</p>

<h2 id="summary">Summary</h2>
<p>I really found Bicep so much better to deal with than ARM templates so if you are on an environment using ARM templates for all your deployments, Bicep will be a really good option to explore and the best of all is that you can get the latest released features so you will not have to wait for a release to start enjoying those new features. Still Bicep needs to mature a bit more to offer more flexibility but you can start getting the benefits already. Modules is something I hope it gets enhanced as this will definitely increase code reusability and will increase authoring time as you will remove copy/paste for different solution deployments</p>

<p>On the other hand If you are already using terraform you will be already enjoying a more mature product that is cloud agnostic in regards to its language. I will assume Bicep team will be making good efforts and make those awesome capabilities from Terraform available in Bicep. Project Bicep still will be native to Azure so it will not be a language you can use on other clouds.</p>

<p>If you are keen I found some motivation on the below great videos.👇</p>

<iframe width="560" height="315" src="https://www.youtube.com/embed/wkQIyenVfxc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<iframe width="560" height="315" src="https://www.youtube.com/embed/exk1QIRwAhU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p>I hope it was informative, hasta la vista and keep working out those 🦾!!!</p>

<h2 id="code-download">Code Download</h2>
<p>The code I created for this demo is available in my blog repository <a href="https://github.com/cerocool1203/blog/tree/main/bicep">Github</a>.</p>

<p><strong>🚴‍♂️ If you enjoyed this blog, you can empower me with some caffeine to continue working in new content 🚴‍♂️.</strong></p>

<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>]]></content><author><name>John Alfaro</name></author><category term="Blog" /><category term="Project Bicep" /><category term="Azure CLI" /><category term="Azure" /><category term="Terraform" /><summary type="html"><![CDATA[This article is demonstrating the deployment of Private AKS using Bicep compared to Terraform]]></summary></entry><entry><title type="html">Terraform Cloud - Test Drive Series</title><link href="https:/blog.johnalfaro.com/blog/TerraformCloud" rel="alternate" type="text/html" title="Terraform Cloud - Test Drive Series" /><published>2020-09-08T00:00:00+10:00</published><updated>2020-09-08T00:00:00+10:00</updated><id>https:/blog.johnalfaro.com/blog/TerraformCloud</id><content type="html" xml:base="https:/blog.johnalfaro.com/blog/TerraformCloud"><![CDATA[<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>

<p>I have been deploying my infrastructure using Terraform Open Source. However, given you can use <a href="https://www.hashicorp.com/products/terraform/pricing/">Terraform Cloud for free</a> you get some features like create and provision infrastructure as well as collaboration which you can do it up to 5 people… which is perfect for what I wanted to achieve. In addition, you get a Terraform Module Registry where you can store and consume modules.</p>

<h2 id="account-creation">Account Creation</h2>

<p>What do I need to get started</p>

<ol>
  <li>Create an Account in  <a href="https://app.terraform.io/signup/account?utm_source=docs_banner">terraform.io</a></li>
  <li>Create a Terraform Cloud Organization</li>
  <li>Have your Version Control System provider for your code integration(I will start with this one but in the long run I think I will be using API-driven workflow due to flexibility to trigger from a more comprehensive pipeline deployment where I can have my infra and my application code)</li>
</ol>

<p>So I decided to open an account and leverage my Version Control System (VCS) account which resides in Github to start exploring a bit deeper. By the time I started the article I already had the integration done but you can follow the <a href="https://www.terraform.io/docs/cloud/vcs/index.html">terraform documentation</a> as you may be using a different VCS provider.</p>

<p>So it kind of looks like this 👇… your own Terraform Cloud (SaaS version)</p>

<p><img src="/assets/images/Blog/2020-09-03/tfc.gif" alt="image-center" class="align-center" /></p>

<h2 id="structure">Structure</h2>

<p>I decided to build a Hub &amp; Spoke scenario where I have three workspaces</p>

<ul>
  <li>
    <p>HUB workspace where I have all my platform layer and NVA appliance  = <code class="language-plaintext highlighter-rouge">cerocool-azure-tenant</code>       👉    Deploys all subscription platform components</p>
  </li>
  <li>
    <p>Sydney Spoke                                                        = <code class="language-plaintext highlighter-rouge">cerocool-application-syd</code>    👉    Deploys infra and application(s) into the Virtual Network (vNet)</p>
  </li>
  <li>
    <p>Melbourne Spoke or Application (just to explorer more)              = <code class="language-plaintext highlighter-rouge">cerocool-application-mel</code>    👉    Deploys infra and application(s) into the Virtual Network (vNet)</p>
  </li>
</ul>

<p>Given that Sydney is the region that usually gets all the love I will deploy most of my infra there as new preview features are released here</p>

<p><img src="/assets/images/Blog/2020-09-03/hub-spoke.jpg" alt="image-center" class="align-center" /></p>

<h2 id="terraform-configuration">Terraform Configuration</h2>

<p>To start consuming those workspaces I did create <code class="language-plaintext highlighter-rouge">cerocool-azure-tenant</code> workspace manually… I guess you always have to start somewhere🤨.. and I have added all the necessary Environment variables from my Azure Service Principal so it can interact with my Azure subscription.</p>

<ul>
  <li>My Service Principal will be carrying tasks for deploying the infrastructure but also to do role assignments so given those requirements I assigned <code class="language-plaintext highlighter-rouge">Owner role</code>.</li>
</ul>

<p>This changes the way I interact from my configuration as well as my backend
<img src="/assets/images/Blog/2020-09-03/opens.jpg" alt="image-center" class="align-center" /></p>

<h2 id="terraform-backend">Terraform Backend</h2>

<p>By default, I was using local backend then I moved to State Storage using an Azure Storage Account which provides state locking…but now I have my state file in Terraform Cloud which support remote operations as for my last two methods all operations were run locally, this is not as key on my environment but on an enterprise environment where multiple people are collaborating this can be key for user experience as you will have a few variables like not relaying on your workstation or scary one sharing credentials to interact with Azure❌</p>

<p>so what’s a <code class="language-plaintext highlighter-rouge">Backend</code>,,, it is a term to define how the state is loaded and how the usual operations are executed like apply, destroy, etc.
<img src="/assets/images/Blog/2020-09-03/backend.jpg" alt="image-center" class="align-center" /></p>

<h2 id="interacting-with-terraform-cloud">Interacting with Terraform Cloud</h2>

<p>After we have some of the requirements met to start deploying your infrastructure. You need to somehow communicate with Terraform Cloud. Given my environment I was able to run <code class="language-plaintext highlighter-rouge">terraform login</code> which is a command released not long ago that allows you obtain automatically an API token. Depending on your environment, you may not be able to use this command so you will generate and save your token on different way. This token gets stored on your local machine and looks like the one below 🤯</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="s2">"credentials"</span><span class="p">:</span> <span class="p">{</span>
    <span class="s2">"app.terraform.io"</span><span class="p">:</span> <span class="p">{</span>
      <span class="s2">"token"</span><span class="p">:</span> <span class="s2">"YOUR OWN TOKEN"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This token is stored on a file called <code class="language-plaintext highlighter-rouge">credentials.tfrc.json</code> which on my Windows laptop is stored on <code class="language-plaintext highlighter-rouge">C:\Users\me\AppData\Roaming\terraform.d\credentials.tfrc.json</code></p>

<p class="notice--info"><strong>Just in case</strong> If you have your own hostname then you need to replace it like <code class="language-plaintext highlighter-rouge">terraform login contoso</code> as by default it is <code class="language-plaintext highlighter-rouge">app.terraform.io</code></p>

<p>After doing all of this we are ready to start planning and applying our Azure infrastructure in this case. Given this is my sandpit environment I did not migrate my state file to Terraform Cloud instead I did start from scratch.</p>

<h2 id="summary">Summary</h2>
<p>This was just an overview of my journey from Terraform Open Source to Terraform Cloud. I will continue on digging and exploring a bit more into some of the benefits you get by using Terraform Cloud for small teams. I have signed for a free trial to explore some of the cool features like cost estimation, sentinel Policies and of course RBAC. I hope this has been informative and helpful⚔</p>

<p><strong>🚴‍♂️ If you enjoyed this blog, you can empower me with some caffeine to continue working in new content 🚴‍♂️.</strong></p>

<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>]]></content><author><name>John Alfaro</name></author><category term="Blog" /><category term="Terraform Cloud" /><category term="Terraform Enterprise" /><category term="Azure" /><summary type="html"><![CDATA[This article provides an idea on how to leverage Terraform Cloud to deploy your Infrastructure as code in Azure]]></summary></entry><entry><title type="html">Terraform 13 - for_each - Test Drive</title><link href="https:/blog.johnalfaro.com/blog/T13-for_each" rel="alternate" type="text/html" title="Terraform 13 - for_each - Test Drive" /><published>2020-08-26T00:00:00+10:00</published><updated>2020-08-26T00:00:00+10:00</updated><id>https:/blog.johnalfaro.com/blog/T13-for_each</id><content type="html" xml:base="https:/blog.johnalfaro.com/blog/T13-for_each"><![CDATA[<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>

<hr />

<h2 id="-terraform-013-the-for_each-feature">🚀 Terraform 0.13: The for_each Feature</h2>

<p>Terraform 0.13 is the latest trend in the Terraform IaC world, and one of its most anticipated features is the new <code class="language-plaintext highlighter-rouge">for_each</code> capability.</p>

<p>I have all my Azure infrastructure currently deployed using Terraform 12. Now that version 13 is available (which, judging by the number, might seem unlucky—but 2020 has already set the bar for “unlucky”!), it’s time to explore the new features. 😄</p>

<hr />

<h2 id="why-is-for_each-great-for-modules">Why Is for_each Great for Modules?</h2>

<p>The <code class="language-plaintext highlighter-rouge">for_each</code> feature allows you to define multiple instances in a single block by using a map, leveraging modules to reduce the amount of code significantly.</p>

<p>I decided to start modifying my infrastructure to take advantage of these new features, beginning with <code class="language-plaintext highlighter-rouge">for_each</code>. You can refer to the <a href="https://github.com/hashicorp/terraform/blob/master/CHANGELOG.md">Terraform 13 Changelog</a> for more information about these changes.</p>

<p>Previously, I had a resource group module that I called <code class="language-plaintext highlighter-rouge">n</code> times—once for each resource group I needed (in my case, nine). It looked like this:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">module</span> <span class="s2">"rg-dmz"</span> <span class="p">{</span>
  <span class="nx">source</span>             <span class="p">=</span> <span class="s2">"../modules/resource_group"</span>
  <span class="nx">name_suffix</span>        <span class="p">=</span> <span class="s2">"dmz"</span>
  <span class="nx">location</span>           <span class="p">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">location</span>
  <span class="nx">full_env_code</span>      <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">full_env_code</span>
  <span class="nx">create</span>             <span class="p">=</span> <span class="kc">true</span>
  <span class="nx">enable_delete_lock</span> <span class="p">=</span> <span class="kc">false</span>
<span class="p">}</span>

<span class="nx">module</span> <span class="s2">"rg-app"</span> <span class="p">{</span>
  <span class="nx">source</span>             <span class="p">=</span> <span class="s2">"../modules/resource_group"</span>
  <span class="nx">name_suffix</span>        <span class="p">=</span> <span class="s2">"app"</span>
  <span class="nx">location</span>           <span class="p">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">location</span>
  <span class="nx">full_env_code</span>      <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">full_env_code</span>
  <span class="nx">create</span>             <span class="p">=</span> <span class="kc">true</span>
  <span class="nx">enable_delete_lock</span> <span class="p">=</span> <span class="kc">false</span>
<span class="p">}</span>
<span class="c1"># ... repeated n times for each RG</span>
</code></pre></div></div>

<hr />

<h2 id="modifications-required">Modifications Required</h2>

<p>To make my code more <strong>efficient</strong> and <strong>dynamic</strong>, I updated my deployment to leverage <code class="language-plaintext highlighter-rouge">for_each</code>. This allows me to define multiple instances in one block using a map, reducing code duplication. More information about using <a href="https://www.terraform.io/docs/configuration/resources.html#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings">for_each</a>.</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">module</span> <span class="s2">"rgs"</span> <span class="p">{</span>
  <span class="nx">for_each</span> <span class="p">=</span> <span class="p">{</span>
    <span class="nx">rg</span><span class="err">-</span><span class="nx">dmz</span>        <span class="p">=</span> <span class="s2">"dmz"</span>
    <span class="nx">rg</span><span class="err">-</span><span class="nx">app</span>        <span class="p">=</span> <span class="s2">"app"</span>
    <span class="nx">rg</span><span class="err">-</span><span class="nx">services</span>   <span class="p">=</span> <span class="s2">"services"</span>
    <span class="nx">rg</span><span class="err">-</span><span class="nx">management</span> <span class="p">=</span> <span class="s2">"management"</span>
    <span class="nx">rg</span><span class="err">-</span><span class="nx">data</span>       <span class="p">=</span> <span class="s2">"data"</span>
    <span class="nx">rg</span><span class="err">-</span><span class="nx">network</span>    <span class="p">=</span> <span class="s2">"network"</span>
    <span class="nx">rg</span><span class="err">-</span><span class="nx">packer</span>     <span class="p">=</span> <span class="s2">"packer"</span>
  <span class="p">}</span>

  <span class="nx">source</span>             <span class="p">=</span> <span class="s2">"../modules/resource_group"</span>
  <span class="nx">name_suffix</span>        <span class="p">=</span> <span class="nx">each</span><span class="err">.</span><span class="nx">value</span>
  <span class="nx">location</span>           <span class="p">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">location</span>
  <span class="nx">full_env_code</span>      <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">full_env_code</span>
  <span class="nx">create</span>             <span class="p">=</span> <span class="kc">true</span>
  <span class="nx">enable_delete_lock</span> <span class="p">=</span> <span class="kc">false</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="state-resource-reorganization">State Resource Reorganization</h2>

<p>After making these changes and running a plan, I encountered a new challenge: the new resources did not match the information in my state file. 🏃‍♂️ Some inspection was required, and I started doing the needful (thanks, BT, for that phrase). However, I had issues moving the state and received the following error:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform state <span class="nb">mv </span>module.rg-packer.azurerm_resource_group.rg  module.rgs[<span class="s2">"rg-packer"</span><span class="o">]</span>.azurerm_resource_group.rg                                     

Error: Index value required

  on  line 1:
  <span class="o">(</span><span class="nb">source </span>code not available<span class="o">)</span>

Index brackets must contain either a literal number or a literal string.

Releasing state lock. This may take a few moments...
</code></pre></div></div>

<p>After some investigation (a.k.a. Googling), I found in the Terraform documentation how to <a href="https://www.terraform.io/docs/commands/state/mv.html#example-move-a-resource-configured-with-for_each">terraform state mv using for_each</a>. Depending on your terminal, you may need different syntax for the state movement. In my case, I was using PowerShell:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform state <span class="nb">mv</span> <span class="s1">'module.rg-packer.azurerm_resource_group.rg[0]'</span> <span class="s1">'module.rgs_mel[\"rg-packer\"].azurerm_resource_group.rg[0]'</span>
</code></pre></div></div>

<p>And voilà! 💃 State movement successful. I repeated this for the rest of my resources.</p>

<p><img src="/assets/images/Blog/2020-08-29/statemv-multiple.jpg" alt="State Movement Success" class="align-center" /></p>

<hr />

<h2 id="calling-the-module-outputs">Calling the Module Outputs</h2>

<p>After completing my state migration and running a plan to ensure everything was correct, I needed to update all statements where I consumed module outputs. For example:</p>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">resource</span> <span class="s2">"azurerm_app_service_plan"</span> <span class="s2">"various"</span> <span class="p">{</span>
  <span class="nx">name</span>                <span class="p">=</span> <span class="s2">"azure-functions-cero-service-plan"</span>
  <span class="nx">location</span>            <span class="p">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">location</span>
  <span class="nx">resource_group_name</span> <span class="p">=</span> <span class="nx">module</span><span class="err">.</span><span class="nx">rgs</span><span class="p">[</span><span class="s2">"rg-management"</span><span class="p">]</span><span class="err">.</span><span class="nx">name</span> <span class="c1"># using the for_each module output</span>
  <span class="nx">kind</span>                <span class="p">=</span> <span class="s2">"FunctionApp"</span>
  <span class="nx">reserved</span>            <span class="p">=</span> <span class="kc">true</span>

  <span class="nx">sku</span> <span class="p">{</span>
    <span class="nx">tier</span> <span class="p">=</span> <span class="s2">"Dynamic"</span>
    <span class="nx">size</span> <span class="p">=</span> <span class="s2">"Y1"</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="the-no-changes-expected-outcome-">The “No Changes Expected” Outcome 👼</h2>

<p>After all these changes, the expected outcome was to have no planned changes, as we were only redesigning the code. My Terraform Cloud workspace applied the changes successfully, and the result was 🧙‍♂️</p>

<p><img src="/assets/images/Blog/2020-08-29/apply_TerraformCloud.jpg" alt="Terraform Cloud Apply" class="align-center" /></p>

<hr />

<h2 id="summary">Summary</h2>

<p>Terraform v13 offers excellent new features that many long-time users have been waiting for. Migrating existing infrastructure to v13 is not always straightforward, but it ultimately makes code management easier. I will continue updating my code to leverage all v13 features and share my progress. I hope this helps someone—Hasta la vista!</p>

<p><strong>🚴‍♂️ If you enjoyed this blog, you can empower me with some caffeine to continue working on new content. 🚴‍♂️</strong></p>

<p><a href="https://www.buymeacoffee.com/cerocool"><img src="https://user-images.githubusercontent.com/1376749/120938564-50c59780-c6e1-11eb-814f-22a0399623c5.png" alt="&quot;Buy Me A Coffee&quot;" /></a></p>]]></content><author><name>John Alfaro</name></author><category term="Blog" /><category term="Terraform Cloud" /><category term="Terraform Enterprise" /><category term="Terraform 13" /><category term="Azure" /><summary type="html"><![CDATA[This article demonstrates how to use for_each in module-centric workflows.]]></summary></entry></feed>