<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Martin Humlund Clausen</title>
    <description>The latest articles on DEV Community by Martin Humlund Clausen (@martinhc).</description>
    <link>https://dev.to/martinhc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1052584%2Fdabedd8e-b1f9-40c2-8070-f7228299c976.JPG</url>
      <title>DEV Community: Martin Humlund Clausen</title>
      <link>https://dev.to/martinhc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/martinhc"/>
    <language>en</language>
    <item>
      <title>Trunk based development: A Teams experiment - From Idea to Practice: Our TBD Workflow</title>
      <dc:creator>Martin Humlund Clausen</dc:creator>
      <pubDate>Mon, 25 Aug 2025 07:37:51 +0000</pubDate>
      <link>https://dev.to/martinhc/trunk-based-development-a-teams-experiment-standard-operating-procedure-j9i</link>
      <guid>https://dev.to/martinhc/trunk-based-development-a-teams-experiment-standard-operating-procedure-j9i</guid>
      <description>&lt;p&gt;Since my last post, our team has started experimenting with Trunk-Based Development (TBD). The early results are promising, and we’ve taken the time to document our process and agreements.&lt;/p&gt;

&lt;p&gt;First things first. Before starting a new way of working, it is super important to get some agreements about responsibilities, and setting expectations. This is an important step in the process.&lt;/p&gt;



&lt;h2&gt;
  
  
  📋 Team Agreements
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Expectation&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Details&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Responsibility for Quality&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You own your code from commit to production impact.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Small, Frequent Commits&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Atomic and meaningful changes only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tag Commits Properly&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Task related work are always tagged. Use AB#&lt;br&gt;- Scout commits are not necessary to tag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;All New Code Must Be Tested&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Preferably with unit tests. Test-Driven Development is Encouraged.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discretion with Breaking Changes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Plan carefully and minimise impact.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reviews are Non-Judgmental&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Focus on the code, not the coder.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Minor Changes by Reviewer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reviewers may commit small fixes directly. (Let the author know)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pull Requests are used selectively&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;- Breaking Changes&lt;br&gt;- Explicit feedback is requested from author&lt;br&gt;- Change require manual test on non-local dev-environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Everyone Reviews&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Regardless of seniority.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Feature Toggles when Needed - (Still gathering knowledge on how to feature-flag)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hide incomplete features, do not delay merging.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As for the agreements, it’s vital that the whole team agrees on the basic premise. &lt;/p&gt;



&lt;h2&gt;
  
  
  🚀 Our Workflow - SOP
&lt;/h2&gt;

&lt;p&gt;So, how does this actually look like in practice?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Direct Commits to Main&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commit directly to main.
&lt;/li&gt;
&lt;li&gt;Use short-lived branches only when absolutely necessary. Those can be:

&lt;ul&gt;
&lt;li&gt;Breaking Changes
&lt;/li&gt;
&lt;li&gt;Explicit Feedback is needed (requested via Pull Request)
&lt;/li&gt;
&lt;li&gt;Required for Manual Tests (if no other way of testing the change)
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Commit Tagging Requirement&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Format: &lt;code&gt;AB#&amp;lt;TaskNumber&amp;gt; &amp;lt;Commit Message&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Example: &lt;code&gt;AB#4321 Add validation for payment processing form&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mandatory Test Coverage for New Code&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All new code must be covered by a test, preferably a unit test.
&lt;/li&gt;
&lt;li&gt;Use the most appropriate automated test type if unit tests are not practical.
&lt;/li&gt;
&lt;li&gt;When changing code in existing codebases with no associated unit tests, create a new unit test class and test that change specifically.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use Discretion with Breaking Changes&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid breaking changes between services.
&lt;/li&gt;
&lt;li&gt;If unavoidable, minimize the impact and number of breaking commits.
&lt;/li&gt;
&lt;li&gt;If a commit is breaking, consider doing a minimal Pull-Request (only 1 commit to rollback if environment breaks).
&lt;/li&gt;
&lt;li&gt;Communicate proactively with affected developers.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Automated Testing First&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build checks, linting, static analysis, unit tests.
&lt;/li&gt;
&lt;li&gt;(Optional) Deploy to Development if needed.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Post-Merge Non-Blocking Code Reviews&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author moves task to “To Review” after merging.
&lt;/li&gt;
&lt;li&gt;Author requests explicit review feedback as comments in the Pull Request, which must be resolved by the reviewer.
&lt;/li&gt;
&lt;li&gt;Reviewer actions:

&lt;ul&gt;
&lt;li&gt;Reviews tagged commits
&lt;/li&gt;
&lt;li&gt;Reviews Pull-Requests
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Reviewers can:

&lt;ul&gt;
&lt;li&gt;Leave comments
&lt;/li&gt;
&lt;li&gt;Make minor fixes directly with a follow-up commit (inform the author)
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Act on Review Feedback Quickly&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Critical issues: fix ASAP.
&lt;/li&gt;
&lt;li&gt;Non-critical issues: address in follow-up work.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Collaborative Testing&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code must be well-tested.
&lt;/li&gt;
&lt;li&gt;Encourage manual exploratory testing by another team member.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; commit often, keep changes small, ensure tests are in place, and use reviews as a collaborative learning tool.&lt;/p&gt;



&lt;h2&gt;
  
  
  💭 Room for Improvements
&lt;/h2&gt;

&lt;p&gt;We consider this a first draft, and there are areas we want to refine as we iterate:&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature Flagging Process
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;While implementing feature flags is straightforward, the challenge lies in managing them over time.
&lt;/li&gt;
&lt;li&gt;Open questions we need to solve:

&lt;ul&gt;
&lt;li&gt;What is the process for introducing and removing feature flags while avoiding long-term technical debt?
&lt;/li&gt;
&lt;li&gt;As a sub-team within a larger organization, how do we align with other teams on consistent use and governance of feature toggles?
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Continuous Delivery Maturity
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pain points:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Currently, we are constrained by rigid approval gates across Development and Live environments.
&lt;/li&gt;
&lt;li&gt;Shared environments introduce dependencies and bottlenecks.
&lt;/li&gt;
&lt;li&gt;Delivery pipelines are standardized across teams, limiting flexibility.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

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

&lt;ul&gt;
&lt;li&gt;Explore ways to streamline approvals while maintaining quality and compliance.
&lt;/li&gt;
&lt;li&gt;Evaluate opportunities for team-specific delivery pipelines or improved coordination mechanisms across teams.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Knowledge Sharing &amp;amp; Adoption Across Teams
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Our team has been able to adopt Trunk-Based Development quickly due to its maturity and seniority. However, other teams may not yet have the same level of experience or comfort with these practices.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pain points:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The company has a strong background in open-source ways of working, which creates some hesitancy and skepticism toward adopting different practices like TBD.
&lt;/li&gt;
&lt;li&gt;Limited visibility into how our process works for teams outside our own.
&lt;/li&gt;
&lt;li&gt;Risk that TBD practices stay siloed within our team instead of being scaled across the organization.
&lt;/li&gt;
&lt;li&gt;Uneven skill levels across teams can slow down adoption.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

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

&lt;ul&gt;
&lt;li&gt;Build confidence internally by gathering more data and experience with TBD in our own team.
&lt;/li&gt;
&lt;li&gt;Document learnings in lightweight, accessible formats (playbooks, demos, internal blog posts).
&lt;/li&gt;
&lt;li&gt;Use side-by-side comparisons with familiar open-source workflows to show how TBD can complement, not replace, existing practices.
&lt;/li&gt;
&lt;li&gt;Run knowledge-sharing sessions to explain the “why” behind our approach.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;This SOP is a living document, and we’ll keep refining it as we go. If you’ve tried Trunk-Based Development in your team, I’d love to hear what worked for you — and what didn’t.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>devops</category>
      <category>learning</category>
      <category>development</category>
    </item>
    <item>
      <title>Trunk based development: A Teams experiment</title>
      <dc:creator>Martin Humlund Clausen</dc:creator>
      <pubDate>Fri, 18 Jul 2025 13:31:08 +0000</pubDate>
      <link>https://dev.to/martinhc/trunk-based-development-a-teams-experiment-1dio</link>
      <guid>https://dev.to/martinhc/trunk-based-development-a-teams-experiment-1dio</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;🚧 Challenging the Pull Request Habit: Our Team’s Trunk-Based Development Experiment&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Coming from a background rooted in open source, RFCs, and collaborative workflows, our company has a strong culture of development through pull requests. PRs are central to how we collaborate, review, and deliver software.&lt;/p&gt;

&lt;p&gt;But recently, I’ve been reflecting on whether this approach still serves us best—especially as a small, high-performing team.&lt;/p&gt;

&lt;p&gt;There’s been a growing body of research, especially from &lt;a href="https://cloud.google.com/devops/state-of-devops" rel="noopener noreferrer"&gt;Google’s State of DevOps (DORA)&lt;/a&gt; and the book &lt;a href="https://www.amazon.com/Accelerate-Software-Performing-Technology-Organizations/dp/1942788339" rel="noopener noreferrer"&gt;Accelerate&lt;/a&gt; by Nicole Forsgren, Jez Humble, and Gene Kim, that highlights an interesting trend:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Teams that practice Trunk-Based Development (TBD)—merging small, frequent commits directly to the mainline—consistently outperform in both speed and stability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sparked a question for me: What if we tried something different?&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;🤔 Why Experiment with Trunk-Based Development?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let me be clear: this isn’t a rejection of PRs. Pull requests are essential in many contexts—especially when introducing potentially breaking changes, managing infrastructure as code, or contributing to open source, or other controlled environments.&lt;/p&gt;

&lt;p&gt;They’re valuable tools, and we’re not throwing them away.&lt;/p&gt;

&lt;p&gt;But in our case—a tight-knit team of three developers—we’ve noticed some subtle but persistent friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PR’s are often merged within a few hours, but they still introduce context switches and unnecessary overhead.&lt;/li&gt;
&lt;li&gt;Review comments tend to focus on minor improvements—naming, formatting, etc. Which could often be addressed later or handled differently.&lt;/li&gt;
&lt;li&gt;We rarely catch logical flaws with the code during reviews&lt;/li&gt;
&lt;li&gt;We still release bugs (albeit minor) into production despite our best efforts&lt;/li&gt;
&lt;li&gt;Each PR brings with it process weight: writing the description, tagging tasks, running manual tests, waiting for reviews, and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Individually, these frictions aren’t catastrophic. But together, they slow us down just enough to matter.&lt;/p&gt;

&lt;p&gt;So, what if we could maintain quality, increase flow, and reduce friction—all while keeping our high trust and collaboration intact?&lt;/p&gt;

&lt;p&gt;My stomach feeling is that when developing new features, you can code 80-90% by isolating specific feature, and only the last percentage is integrating with existing code and services. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;🧪 The Experiment: A Shift to Trunk-Based Development&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We’re running a TBD experiment over a few sprints. It’s structured, intentional, and driven by data. Our goals are to improve lead time, reduce cognitive load, and boost developer satisfaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ What We’re Testing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Refined Process&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Share a developer brief to introduce the experiment and invite team reflection.&lt;/li&gt;
&lt;li&gt;Establish team agreements on how we’ll operate.&lt;/li&gt;
&lt;li&gt;Pull requests are still being used—but used selectively, especially when code requires manual testing or broader review, or integration.&lt;/li&gt;
&lt;li&gt;Tagging of commits with task IDs for traceability, and to support non-blocking reviews.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Lean Into CI/CD&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated testing is mandatory for all new code—preferably unit tests, but anything that makes sense contextually and is automated&lt;/li&gt;
&lt;li&gt;Any commit that passes CI is eligible for deployment.&lt;/li&gt;
&lt;li&gt;Emphasise tiny, frequent commits and early integration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Non-Blocking Reviews&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reviews are asynchronous and post-merge where appropriate.&lt;/li&gt;
&lt;li&gt;Feedback is delivered directly to authors, especially for more significant changes.&lt;/li&gt;
&lt;li&gt;Minor improvements can be committed directly to main by reviewer.&lt;/li&gt;
&lt;li&gt;The focus shifts from gatekeeping to enabling flow and improvement.&lt;/li&gt;
&lt;li&gt;Boyscout commits, such as removing unused namespaces, updating variable names or whatever leaves the campsite cleaner does not need to be part of the review process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Weekly Team Check-ins&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each week we ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What friction points have emerged?&lt;/li&gt;
&lt;li&gt;Are we moving faster—or slower?&lt;/li&gt;
&lt;li&gt;Have we introduced or reduced bugs?&lt;/li&gt;
&lt;li&gt;Can we replace manual steps with automation?&lt;/li&gt;
&lt;li&gt;How do we feel about the process overall?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;📈 Metrics We’re Tracking&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Lead time from commit to production&lt;/li&gt;
&lt;li&gt;Deployment frequency&lt;/li&gt;
&lt;li&gt;Rate of bugs or rollbacks&lt;/li&gt;
&lt;li&gt;Team sentiment and confidence in the process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn’t a leap of faith—it’s a measured experiment. We’re testing, learning, and adapting.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;🌱 The Feeling of Growth&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Pull requests have become more than a workflow—they’re a symbol of caution, structure, and trust. In large or distributed teams, they play an essential role.&lt;/p&gt;

&lt;p&gt;But in small, high-trust environments, those same rituals can become bottlenecks. And the truth is: what worked five years ago might not be the right fit for who we are now.&lt;/p&gt;

&lt;p&gt;This is a moment to reflect, evolve, and improve.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;🧭 In Closing&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I don’t know yet if Trunk-Based Development will be the silver bullet for us. (Is anything really?)&lt;/p&gt;

&lt;p&gt;But I do know this: it’s worth exploring.&lt;/p&gt;

&lt;p&gt;We’re not abandoning quality—we’re doubling down on trust, tooling, and transparency.&lt;/p&gt;

&lt;p&gt;If you’re in a small team and feel the drag of over-engineered process, maybe it’s time to ask: What are we optimising for?&lt;/p&gt;

&lt;p&gt;For me its all about running the experiement, gather the data and the feedback, and if it does not work, the worst thing that happens is that we go back to the “old” ways of working.&lt;/p&gt;

&lt;p&gt;Let’s see what happens. I promise to do a followup&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>development</category>
      <category>devops</category>
      <category>learning</category>
    </item>
    <item>
      <title>Investigating Azure Regional Capabilities</title>
      <dc:creator>Martin Humlund Clausen</dc:creator>
      <pubDate>Sun, 22 Sep 2024 08:35:21 +0000</pubDate>
      <link>https://dev.to/martinhc/investigating-azure-regional-capabilities-3nlp</link>
      <guid>https://dev.to/martinhc/investigating-azure-regional-capabilities-3nlp</guid>
      <description>&lt;p&gt;This week, I was tasked with figuring out whether we could support a new region on our company’s hosting platform.&lt;/p&gt;

&lt;p&gt;I started by searching the web for information on Azure capabilities in a specific region. Before I knew it, I had spent an hour combing through resources. The only useful document I could find was a huge, 50-page PDF filled with slick layouts, graphics, and all the marketing fluff you can imagine. While the PDF did contain some technical information, I quickly realised it would take forever to compare the resources in the document with those from an existing region we were already using.&lt;/p&gt;

&lt;p&gt;It was time to switch up my approach.&lt;/p&gt;

&lt;p&gt;I decided to explore the Azure CLI to see if I could query regions and their capabilities directly. Sometimes when you ask the universe for answers, it delivers. After a bit of planning, I came up with the following approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, we need to list the current capabilities in an existing region to get a list of &lt;code&gt;resourceType&lt;/code&gt; we require.&lt;/li&gt;
&lt;li&gt;Then, we’ll retrieve the provider information for a specific &lt;code&gt;resourceType&lt;/code&gt; and check whether it’s available in the target region. For this example, we’ll use UAE North for this example.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sound fun? Let’s dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  List Resources for existing Region
&lt;/h2&gt;

&lt;p&gt;Sign into azure using Azure Cli and switch to the subscription containing your region.&lt;/p&gt;

&lt;p&gt;Note: We have architected Azure to have one subscription per region, so its fairly easy for us to manage regional resources and cost associated with a specific market. I digress..&lt;/p&gt;

&lt;p&gt;So…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--subscription&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;name of your regional subscription&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Getting a flat list by querying all types of resources, and then sort them by unique entry&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[].type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tsv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output the following list&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Microsoft.AppConfiguration/configurationStores,
Microsoft.Automation/automationAccounts,
Microsoft.Automation/automationAccounts/runbooks,
Microsoft.Compute/virtualMachineScaleSets,
Microsoft.ContainerInstance/containerGroups,
Microsoft.ContainerService/managedClusters
... etc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Listing regional capabilities
&lt;/h2&gt;

&lt;p&gt;From here I concocted the following powershell script to iterate over existing resource types, and check whether they were available in the target region, Add the result to a list, and then output the result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List of resource providers and types for an regional Stamp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$resourceTypes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Microsoft.AppConfiguration/configurationStores"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Microsoft.Automation/automationAccounts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Microsoft.Automation/automationAccounts/runbooks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Microsoft.Compute/virtualMachineScaleSets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Microsoft.ContainerInstance/containerGroups"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Microsoft.ContainerService/managedClusters"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Region to check (Full name of the region)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$region&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UAE North"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$resultList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Iterate over the resource types and check availability&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$resourceType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$resourceTypes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Checking &lt;/span&gt;&lt;span class="nv"&gt;$resourceType&lt;/span&gt;&lt;span class="s2"&gt; in &lt;/span&gt;&lt;span class="nv"&gt;$region&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nv"&gt;$providerNamespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$resourceType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$resourceTypeName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$resourceType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="c"&gt;# Get provider information&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$providerInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$providerNamespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resourceTypes[?resourceType=='&lt;/span&gt;&lt;span class="nv"&gt;$resourceTypeName&lt;/span&gt;&lt;span class="s2"&gt;']"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$providerInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$locations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$providerInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locations&lt;/span&gt;&lt;span class="w"&gt;

        &lt;/span&gt;&lt;span class="nv"&gt;$doesExist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$locations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-contains&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$region&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$symbol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$doesExist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"✔"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"❌"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

        &lt;/span&gt;&lt;span class="nv"&gt;$resultList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PSCustomObject&lt;/span&gt;&lt;span class="p"&gt;]@{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nx"&gt;ResourceType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$resourceType&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nx"&gt;DoesExist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$symbol&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Could not retrieve information for &lt;/span&gt;&lt;span class="nv"&gt;$resourceType&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$resultList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Format-Table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AutoSize&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script outputs the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Name                                               DoesExist
&lt;span class="nt"&gt;----&lt;/span&gt;                                               &lt;span class="nt"&gt;---------&lt;/span&gt;
Microsoft.AppConfiguration/configurationStores     ✔
Microsoft.Automation/automationAccounts            ✔
Microsoft.Automation/automationAccounts/runbooks   ✔
Microsoft.Compute/virtualMachineScaleSets          ✔
Microsoft.ContainerInstance/containerGroups        ✔
Microsoft.ContainerService/managedClusters         ✔
Microsoft.DocumentDB/databaseAccounts              ✔
Microsoft.Insights/actiongroups                    ❌
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;While we discovered that we were unable to create certain resources in a specific region, it quickly gave us the knowledge needed to provide the business with an assessment of whether we could launch in that region.&lt;/p&gt;

&lt;p&gt;On a personal level, I feel like this exercise gave me more insight into the capabilities available through the Azure CLI command. Additionally, I spent roughly 5 hours on the task in total. Next time the business asks if we can launch in a region, we’ll be able to provide an answer even faster.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>devops</category>
      <category>productivity</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Productivity hack of the day: Creating pull requests descriptions from Git diff and ChatGPT</title>
      <dc:creator>Martin Humlund Clausen</dc:creator>
      <pubDate>Tue, 10 Sep 2024 06:57:31 +0000</pubDate>
      <link>https://dev.to/martinhc/productivity-hack-of-the-day-creating-pull-requests-descriptions-from-git-diff-1pm0</link>
      <guid>https://dev.to/martinhc/productivity-hack-of-the-day-creating-pull-requests-descriptions-from-git-diff-1pm0</guid>
      <description>&lt;p&gt;As developers, we create a lot of pull requests, and I must admit that sometimes I neglect to write a detailed description to help my team review the changes efficiently.&lt;/p&gt;

&lt;p&gt;To assist my team, I use ChatGPT to generate richer pull request descriptions, giving me a little more time to focus on coding.&lt;/p&gt;

&lt;p&gt;Here’s the simple trick:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On your current feature branch, run the following PowerShell script from within your root repository folder. It will output a &lt;code&gt;diff.patch&lt;/code&gt; file .&lt;/li&gt;
&lt;li&gt;Paste that file into ChatGPT and use the following prompt:

&lt;ul&gt;
&lt;li&gt;"As a software developer, I’ve made the changes in the attached git diff file. Please write a pull request description for my team to help them review it efficiently, with an emphasis on &lt;em&gt;{Insert your bullet points}&lt;/em&gt;."&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the &lt;a href="https://gist.github.com/mclausen/de3eb9eded937b7bbd47d8ec3915be39" rel="noopener noreferrer"&gt;gist&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check if the git command is available&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ErrorAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SilentlyContinue&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Git is not installed or not available in the PATH."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ForegroundColor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Red&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;exit&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Ensure we're in a git repository&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$gitStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rev-parse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--is-inside-work-tree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$gitStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ne&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This is not a git repository."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ForegroundColor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Red&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;exit&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Get the current branch name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$currentBranch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rev-parse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--abbrev-ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HEAD&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$currentBranch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You are on the 'main' branch. Please switch to a feature branch."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ForegroundColor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Yellow&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;exit&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Fetch the latest changes for the main branch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Get the latest commit on the main branch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$mainCommit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rev-parse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;origin/main&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Get the latest commit on the current branch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$currentBranchCommit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rev-parse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HEAD&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Generate the diff between the latest commit on main and the current branch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$diffFileName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"diff.patch"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$mainCommit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$currentBranchCommit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$diffFileName&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Check if the diff file was generated successfully&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$diffFileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Diff file generated successfully: &lt;/span&gt;&lt;span class="nv"&gt;$diffFileName&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ForegroundColor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Green&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Failed to generate diff file."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ForegroundColor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Red&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a generel rule of thumb, keep your Pull Request small! Not only does that help your team reviewing your changes and it also makes writing Pull request descriptions easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;As alway, when using gpt is that you OWN the output, so dont forget to check it.&lt;/li&gt;
&lt;li&gt;The output is only for scaffolding a pull request description, so modify the output accordingly&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>productivity</category>
      <category>chatgpt</category>
      <category>git</category>
      <category>ai</category>
    </item>
    <item>
      <title>Deploying Terraform Infrastructure to Azure using Azure DevOps Pipelines</title>
      <dc:creator>Martin Humlund Clausen</dc:creator>
      <pubDate>Mon, 28 Aug 2023 10:16:59 +0000</pubDate>
      <link>https://dev.to/martinhc/deploying-terraform-infrastructure-to-azure-using-azure-devops-pipelines-3kpi</link>
      <guid>https://dev.to/martinhc/deploying-terraform-infrastructure-to-azure-using-azure-devops-pipelines-3kpi</guid>
      <description>&lt;p&gt;In my experience knowing how to deploy a service intro production is as important as knowing the application itself. As Software Engineers we’ll like to manage the risk of deployments, and deploy as often and early as possible in the release cycle without downtime. However, every now and then you have to deploy infrastructure as well, so why not subject it, to the same level of automation as your applications?&lt;/p&gt;

&lt;p&gt;This is the Second part of how to use Terraform with Azure, so if you haven’t already go read my &lt;a href="https://dev.to/martinhc/getting-started-with-terraform-and-azure-34ng"&gt;first blog entry&lt;/a&gt; on how to setup a terraform repository and create the necessary service principles to get your started. Alternatively you can go to the repository on &lt;a href="https://github.com/mclausen/Cloud.Platform.Foundation/tree/main/samples/01-initial-terraform-with-azure-backend" rel="noopener noreferrer"&gt;Github&lt;/a&gt; see what is there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What kind of Infrastructure are we deploying?
&lt;/h2&gt;

&lt;p&gt;When working with bigger Cloud Platforms you quickly discover that not all infrastructure are deployed at the same time, and more often than not, we gave contain multiple layers of infrastructure. To drive the point through consider these two layers&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Global Infrastructure, this typically includes changes to an Azure Kubernetes Cluster, a Azure Container Registry, or an  Azure Service Bus. Global Infrastructure is the shared components that your applications can leverage. Also global infrastructure can be deployed independently of the rest of your application stack.&lt;/li&gt;
&lt;li&gt;Service Infrastructure is the the infrastructure that is needed to run your application, this includes Databases, Redis cache etc. And is deployed together with the service deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our organisation we have a couple of more layers, but lets just keep it simple for demonstration purpose, so for this article, I would like to focus on the global infrastructure, since we its easy to zoom in on the pipeline itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does our deployment pipeline look like?
&lt;/h2&gt;

&lt;p&gt;For this sample pipeline we are going to build two stages&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate Terraform - First stage we validate that our terraform configuration is correctly configured. This includes Terraform syntax and connectivity to our Azure Backend Store. &lt;/li&gt;
&lt;li&gt;Release to Dev - We have some infrastructure to deploy, so lets release it to our development environment&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;I have chosen to organizing the pipeline in the following way.&lt;/p&gt;

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

├── azure-pipelines.yaml
├── main.yaml
└── stages
    ├── tf-release.yaml
    └── tf-validate.yaml


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;azure-pipelines.yaml&lt;/code&gt; is the entry point for the pipelines. It contains all the triggers, variables and other pipeline controls&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;main.yaml&lt;/code&gt; can be though of an orchestrator of stages. It contains a list stages that we would like to execute&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tf-release.yaml&lt;/code&gt; is our terraform release stage, that is responsible for doing the actual release of terraform. Note here that we do not denote the environment as to which we would like to deploy. We can simply reuse the stage and give it other parameters on runtime.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tf-validate.yaml&lt;/code&gt; is or Terraform Validation Stage&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deploying your infrastructure
&lt;/h2&gt;

&lt;p&gt;All the magic happens in the &lt;code&gt;tf-release.yaml&lt;/code&gt;. Here we basically do the exact same things, as if we had run the command locally.&lt;/p&gt;

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

terraform init
terraform plan
terraform apply


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

&lt;/div&gt;

&lt;p&gt;Starting at the file, we define three variables&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tf_version&lt;/code&gt; - We need to parse in the terraform version into the release stage to ensure that our whole pipeline is using the same version of terraform. Since we use terraform in multiple pipelines this variable is defined in the azure-pipelines.yaml and parsed through all stages&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;working_directory&lt;/code&gt; - this will be our base directory where we are executing commands&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;environment&lt;/code&gt; - is the name of the environment. We can use this to do some magic display names, or reference azure pipelines variables using naming conventions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally I will be pulling in, the variable group that we created in the  to get the service principle along with the access key to our storage account, plus three additional variables to help with location of our terraform artifacts.&lt;/p&gt;

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

parameters:
  tf_version:
  working_directory:
  environment:

stages:
  - stage: release_&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ parameters.environment &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
    displayName: Release to &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ parameters.environment &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
    variables:
      - group: root-terraform-backend-credentials
      - name: backend_tfvars
        value: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ parameters.working_directory &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}/tf/backend/backend.ci.tfvars"&lt;/span&gt;
      - name: variable_tfvars
        value:  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ parameters.working_directory &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}/tf/environments/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ parameters.environment &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}/variables.tfvars"&lt;/span&gt;
      - name: infrastructure_workingdirectory
        value:  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ parameters.working_directory &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}/tf"&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Install Terraform on the build agent
&lt;/h3&gt;

&lt;p&gt;Until we have explicitly installed terraform on our build agent, we’ll not be able to execute any Terraform. This is however pretty straight forward. Though you might have to install &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-devlabs.custom-terraform-tasks" rel="noopener noreferrer"&gt;Terraform Extension&lt;/a&gt; into your Azure DevOps Organization&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

- task: TerraformInstaller@0
  displayName: &lt;span class="s2"&gt;"Use Terraform &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ parameters.tf_version &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
  inputs:
    terraformVersion: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ parameters.tf_version &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Terraform Init
&lt;/h3&gt;

&lt;p&gt;When initialising terraform we must provide it the initial backend file that we would like to use. We first have to replace the tokens in our &lt;code&gt;backend.ci.tfvar&lt;/code&gt; file. You might also have to install  &lt;a href="https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens" rel="noopener noreferrer"&gt;Replace Tokens&lt;/a&gt; Extension into your Azure DevOps Organization.&lt;/p&gt;

&lt;p&gt;After we have replace the token, we can proceed to initializing terraform&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

- task: qetza.replacetokens.replacetokens-task.replacetokens@3
  displayName: &lt;span class="s2"&gt;"Replace tokens in backend.tfvars with variables from the CI/CD environment vars"&lt;/span&gt;
  inputs:
    targetFiles: &lt;span class="si"&gt;$(&lt;/span&gt;backend_tfvars&lt;span class="si"&gt;)&lt;/span&gt;
    encoding: &lt;span class="s2"&gt;"auto"&lt;/span&gt;
    writeBOM: &lt;span class="nb"&gt;true
    &lt;/span&gt;actionOnMissing: &lt;span class="s2"&gt;"fail"&lt;/span&gt;
    keepToken: &lt;span class="nb"&gt;false
    &lt;/span&gt;tokenPrefix: &lt;span class="s2"&gt;"#{"&lt;/span&gt;
    tokenSuffix: &lt;span class="s2"&gt;"}#"&lt;/span&gt;

- bash: |
    terraform init &lt;span class="nt"&gt;-backend-config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;backend_tfvars&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false
  env&lt;/span&gt;:
    ARM_CLIENT_ID: &lt;span class="si"&gt;$(&lt;/span&gt;sp_clientId&lt;span class="si"&gt;)&lt;/span&gt;
    ARM_CLIENT_SECRET: &lt;span class="si"&gt;$(&lt;/span&gt;sp_clientSecret&lt;span class="si"&gt;)&lt;/span&gt;
    ARM_SUBSCRIPTION_ID: &lt;span class="si"&gt;$(&lt;/span&gt;sp_subscriptionId&lt;span class="si"&gt;)&lt;/span&gt;
    ARM_TENANT_ID: &lt;span class="si"&gt;$(&lt;/span&gt;sp_tenantId&lt;span class="si"&gt;)&lt;/span&gt;
  displayName: Initialize configuration
  workingDirectory: &lt;span class="si"&gt;$(&lt;/span&gt;infrastructure_workingdirectory&lt;span class="si"&gt;)&lt;/span&gt;
  failOnStderr: &lt;span class="nb"&gt;true&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;See the &lt;code&gt;-input=false&lt;/code&gt; flag at the end? This tells Terraform that it should not prompt for input, and is important not to miss when running in a CI environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terraform Plan
&lt;/h3&gt;

&lt;p&gt;Terraform plan, is the action of making the change set for our infrastructure. For this command we are parsing providing a &lt;code&gt;-out&lt;/code&gt; parameter which is a mechanism to store that change set to a file on disk, and since this is running in a single build agent job, we can use that file to apply the infrastructure in a later task. &lt;/p&gt;

&lt;p&gt;💡 You can export the terraform plan file and use it in another stage. This is especially useful if you want to add an additional approval step, or inspect the file manually.&lt;/p&gt;


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

&lt;ul&gt;
&lt;li&gt;bash: |
terraform plan &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;variable_tfvars&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan
&lt;span class="nb"&gt;env&lt;/span&gt;:
ARM_CLIENT_ID: &lt;span class="si"&gt;$(&lt;/span&gt;sp_clientId&lt;span class="si"&gt;)&lt;/span&gt;
ARM_CLIENT_SECRET: &lt;span class="si"&gt;$(&lt;/span&gt;sp_clientSecret&lt;span class="si"&gt;)&lt;/span&gt;
ARM_SUBSCRIPTION_ID: &lt;span class="si"&gt;$(&lt;/span&gt;sp_subscriptionId&lt;span class="si"&gt;)&lt;/span&gt;
ARM_TENANT_ID: &lt;span class="si"&gt;$(&lt;/span&gt;sp_tenantId&lt;span class="si"&gt;)&lt;/span&gt;
displayName: Create execution plan
workingDirectory: &lt;span class="si"&gt;$(&lt;/span&gt;infrastructure_workingdirectory&lt;span class="si"&gt;)&lt;/span&gt;
failOnStderr: &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Terraform Apply&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;Last thing we need to do is to apply the changes. For this example, we parse the &lt;code&gt;-auto-approve&lt;/code&gt; flag, which will just apply the changes that we have, without prompting us. Additionally we are using the &lt;code&gt;tfplan&lt;/code&gt; that we created in the previous task.&lt;/p&gt;


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

&lt;ul&gt;
&lt;li&gt;bash: |
terraform apply &lt;span class="nt"&gt;-input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;-auto-approve&lt;/span&gt; tfplan
terraform output &lt;span class="nt"&gt;-json&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; output.&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ parameters.environment &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;.json
&lt;span class="nb"&gt;env&lt;/span&gt;:
ARM_CLIENT_ID: &lt;span class="si"&gt;$(&lt;/span&gt;sp_clientId&lt;span class="si"&gt;)&lt;/span&gt;
ARM_CLIENT_SECRET: &lt;span class="si"&gt;$(&lt;/span&gt;sp_clientSecret&lt;span class="si"&gt;)&lt;/span&gt;
ARM_SUBSCRIPTION_ID: &lt;span class="si"&gt;$(&lt;/span&gt;sp_subscriptionId&lt;span class="si"&gt;)&lt;/span&gt;
ARM_TENANT_ID: &lt;span class="si"&gt;$(&lt;/span&gt;sp_tenantId&lt;span class="si"&gt;)&lt;/span&gt;
displayName: Apply execution plan
workingDirectory: &lt;span class="si"&gt;$(&lt;/span&gt;infrastructure_workingdirectory&lt;span class="si"&gt;)&lt;/span&gt;
failOnStderr: &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Conclusion&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;In my previous article &lt;a href="https://dev.to/martinhc/getting-started-with-terraform-and-azure-34ng"&gt;Getting Started with Terraform and Azure&lt;/a&gt;, we started by setting up the the initial terraform, which would serve as the foundation for constructing a Cloud Platform using Terraform. &lt;/p&gt;

&lt;p&gt;For this article we have gone through how to apply the infrastructure using Azure Pipelines.&lt;/p&gt;

&lt;p&gt;This is a very basic example, and I am sure that you need to do adjustments to fit your development flow. However here are some ideas, that you can consider for your next project&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have the pipeline add an additional approval step before applying the infrastructure structure&lt;/li&gt;
&lt;li&gt;Have the multistage Azure Pipeline, Plan your live environment, after you have deployed to Development and see how the change will affect your live environment, before approving it.&lt;/li&gt;
&lt;li&gt;Configure a web hook that tells your team what changes are being deployed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the full code sample on &lt;a href="https://github.com/mclausen/Cloud.Platform.Foundation/tree/main/samples/01-initial-terraform-with-azure-backend" rel="noopener noreferrer"&gt;Github&lt;/a&gt;, with more examples to come.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Github Repository: &lt;a href="https://github.com/mclausen/Cloud.Platform.Foundation/tree/main/samples/01-initial-terraform-with-azure-backend" rel="noopener noreferrer"&gt;Cloud.Platform.Foundation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Azure DevOps Extension: &lt;a href="https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens" rel="noopener noreferrer"&gt;ReplaceTokens&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Azure DevOps Extension: &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-devlabs.custom-terraform-tasks" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>azure</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Getting Started with Terraform and Azure</title>
      <dc:creator>Martin Humlund Clausen</dc:creator>
      <pubDate>Sat, 19 Aug 2023 09:23:01 +0000</pubDate>
      <link>https://dev.to/martinhc/getting-started-with-terraform-and-azure-34ng</link>
      <guid>https://dev.to/martinhc/getting-started-with-terraform-and-azure-34ng</guid>
      <description>&lt;p&gt;At the heart of any Cloud Platform is Infrastructure, and how we manage infrastructure in large-scale systems has a direct correlation with how effective your development teams are when deploying, securing, and auditing your infrastructure.&lt;/p&gt;

&lt;p&gt;For this article, I will go through the simple steps for setting up Terraform and connecting the state to an Azure Blob Storage Account.&lt;/p&gt;

&lt;p&gt;A huge disclaimer here is that I am possibly suffering from a severe case of imposter syndrome and that I am treating grounds that so many developers have walked the path before me. There is notes on how to get started, and I hope that you find them useful.&lt;/p&gt;

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

&lt;p&gt;We are of course using some command line magic to get us started, so before you go any further make sure to have the following installed command line tools on your dev box&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure CLI&lt;/li&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;An Azure Subscription with Contributor rights, and access to Azure DevOps&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;Infrastructure as Code is an addiction. You discover that all that complicated stuff, that you have used to setup manually either on Amazon, Azure, or even Cloudflare, can be coded and managed via Infrastructure as Code, you have an unlimited amount of terraform providers, and you discover that it supports everything that you are trying to build! That is freaking awesome!&lt;/p&gt;

&lt;p&gt;However, you need to start somewhere and you cannot start using Terraform and not have either a state file locally, or somewhere else. That means, that there is a small amount of infrastructure that you still have to manually create in order to get started. &lt;/p&gt;

&lt;p&gt;Since this is an Azure tutorial, this tutorial will cover setting up the foundational stuff that you need in order to execute Terraform from a CI/CD Pipeline.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Azure Subscription that will contain all the configured resources, that we need to get started&lt;/li&gt;
&lt;li&gt;A Service Principle with Contributor rights to the Subscription&lt;/li&gt;
&lt;li&gt;A Manually Created Storage Account to Store the Terraform state&lt;/li&gt;
&lt;li&gt;An Azure DevOps variable group, to store the service principle and the Storage Account Access Key&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating Service Principle
&lt;/h2&gt;

&lt;p&gt;In order to execute our terraform you need to be able to access your Azure Subscription with Contributor access rights. &lt;/p&gt;

&lt;p&gt;If you want to work with Terraform locally it is enough to log in via the Azure CLI&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;And switch to the Subscription that you are currently working with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az account &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--subscription&lt;/span&gt; &amp;lt;SubscriptionId&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However we would like to have our terraform executed via an automated build pipeline, and for that, we need to create a &lt;a href="https://learn.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli" rel="noopener noreferrer"&gt;Service Principle&lt;/a&gt;. Long story short, a service principle is an identity, that has been assigned scopes to a particular subscription, and that Identity can be used to interact with Azure. &lt;/p&gt;

&lt;p&gt;Spin up your favorite command line and lets us create the service principal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az ad sp create-for-rbac &lt;span class="nt"&gt;--display-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"root-terraform-sp"&lt;/span&gt; &lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Contributor"&lt;/span&gt; &lt;span class="nt"&gt;--scopes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/42e5d700***"&lt;/span&gt;

&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"appId"&lt;/span&gt;: &lt;span class="s2"&gt;"519ef410****"&lt;/span&gt;,
  &lt;span class="s2"&gt;"displayName"&lt;/span&gt;: &lt;span class="s2"&gt;"root-terraform-sp"&lt;/span&gt;,
  &lt;span class="s2"&gt;"password"&lt;/span&gt;: &lt;span class="s2"&gt;"JKX8Q~*****"&lt;/span&gt;,
  &lt;span class="s2"&gt;"tenant"&lt;/span&gt;: &lt;span class="s2"&gt;"e4794ea3-****"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up the Storage Account for the Terraform Backend
&lt;/h2&gt;

&lt;p&gt;By default, Terraform will be storing your state locally on your development box, which isn’t ideal if you want to either manage your Infrastructure via the build pipeline (because it cannot access your local store DUH!). Additionally, you will never be able to recover the state files should the laptop go missing. So let's put it somewhere on the Cloud.&lt;/p&gt;

&lt;p&gt;Setting up an Azure Storage Account to manage our state puts us in a chicken and an egg problem. We’d want to use Terraform to manage our infrastructure, but we need some infrastructure to store our state. My recommendation is just to create a resource group and the storage account manually, and not think too much about it.&lt;/p&gt;

&lt;p&gt;This is the snipped is shamelessly copied from the documentation linked below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;RESOURCE_GROUP_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfstate
&lt;span class="nv"&gt;STORAGE_ACCOUNT_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfstate&lt;span class="nv"&gt;$RANDOM&lt;/span&gt;
&lt;span class="nv"&gt;CONTAINER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfstate

&lt;span class="c"&gt;# Create resource group&lt;/span&gt;
az group create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP_NAME&lt;/span&gt; &lt;span class="nt"&gt;--location&lt;/span&gt; eastus

&lt;span class="c"&gt;# Create storage account&lt;/span&gt;
az storage account create &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP_NAME&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$STORAGE_ACCOUNT_NAME&lt;/span&gt; &lt;span class="nt"&gt;--sku&lt;/span&gt; Standard_LRS &lt;span class="nt"&gt;--encryption-services&lt;/span&gt; blob

&lt;span class="c"&gt;# Create blob container&lt;/span&gt;
az storage container create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINER_NAME&lt;/span&gt; &lt;span class="nt"&gt;--account-name&lt;/span&gt; &lt;span class="nv"&gt;$STORAGE_ACCOUNT_NAME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that is done, you can get the storage Access Key, and store this somewhere secure&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ACCOUNT_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az storage account keys list &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP_NAME&lt;/span&gt; &lt;span class="nt"&gt;--account-name&lt;/span&gt; &lt;span class="nv"&gt;$STORAGE_ACCOUNT_NAME&lt;/span&gt; &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'[0].value'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$ACCOUNT_KEY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up a basic Terraform
&lt;/h2&gt;

&lt;p&gt;The initial structure for Terraform project is simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
├── backend
│   ├── backend.ci.tfvars
│   └── backend.local.tfvars
├── environments
│   ├── dev
│   │   └── variables.tfvars
│   └── live
│       └── variables.tfvars
├── main.tf
├── providers.tf
└── variables.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the top level, we have the expected &lt;a href="http://main.tf" rel="noopener noreferrer"&gt;main.tf&lt;/a&gt; and &lt;a href="http://providers.tf" rel="noopener noreferrer"&gt;providers.tf&lt;/a&gt; for configuring our terraform modules, and secondly, we have a list of environments that we will be targeting. Each environment has a set of variables and an associated &lt;a href="http://backend.tf" rel="noopener noreferrer"&gt;backend.ci.tf&lt;/a&gt;vars file&lt;/p&gt;

&lt;p&gt;Assuming that you would like to use Azure as a provider, then I’ve configured the &lt;a href="http://provider.tf" rel="noopener noreferrer"&gt;provider.tf&lt;/a&gt; to look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform &lt;span class="o"&gt;{&lt;/span&gt;
  required_providers &lt;span class="o"&gt;{&lt;/span&gt;
    azurerm &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;source&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/azurerm"&lt;/span&gt;
      version &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 3.69.0"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  required_version &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.5.5"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

provider &lt;span class="s2"&gt;"azurerm"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  features &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Backend File
&lt;/h3&gt;

&lt;p&gt;From here it's time to set up terraforms  &lt;strong&gt;backend.local.tfvars&lt;/strong&gt; file, with the following. The idea behind the file is to have a local copy that you can work with, so make sure that this file does not leave your machine, and preferably not part of your git repository!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
resource_group_name  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tfstate"&lt;/span&gt;
storage_account_name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;storage_account_name&amp;gt;"&lt;/span&gt;
container_name       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tfstate"&lt;/span&gt;
key                  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform.tfstate"&lt;/span&gt;
access_key           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;your account key&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are going to create a new &lt;strong&gt;backend.ci.tfvars&lt;/strong&gt; file, that contains replaceable strings that are being used from CI/CD&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
resource_group_name  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tfstate"&lt;/span&gt;
storage_account_name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;storage_account_name&amp;gt;"&lt;/span&gt;
container_name       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tfstate"&lt;/span&gt;
key                  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform.tfstate"&lt;/span&gt;
access_key           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"{tf_backend_storage_account_key}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating an Azure Pipelines Variable group
&lt;/h2&gt;

&lt;p&gt;In order to access these variables from our Azure Pipelines we need to create new Azure pipelines variable group, where we can store our service principal and our storage account access key.&lt;/p&gt;

&lt;p&gt;Spin up your favorite Azure DevOps Project, Go to Pipeline, Go to Library, and Click the plus sign.&lt;/p&gt;

&lt;p&gt;From here fill in the information for our service principle and our storage account access key.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Deploying your Terraform using Azure Pipelines
&lt;/h2&gt;

&lt;p&gt;The whole point of us creating all this infrastructure as code, is that our infrastructure is audited, and not changed by humans directly, so of course we would like our infrastructure to be executed automatically whenever we push infrastructure changes to our repository.&lt;/p&gt;

&lt;p&gt;More on that in an upcoming article.&lt;/p&gt;

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

&lt;p&gt;So for the challenges we set up an initial terraform repository, we have configured a backend.ts that is configured to use Azure Storage Account as a backend Store. Additionally, we have configured a Service Principle that we can use to execute Terraform from Azure Pipelines, and set up an Azure Pipelines Variable Group to store the information.&lt;/p&gt;

&lt;p&gt;I hope that this was enough to get you started! &lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;The documentation information found in this article is already out there&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sample Repo: &lt;a href="https://github.com/mclausen/Cloud.Platform.Foundation" rel="noopener noreferrer"&gt;https://github.com/mclausen/Cloud.Platform.Foundation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Setting up Azure Storage Account for Terraform: &lt;a href="https://learn.microsoft.com/en-us/azure/developer/terraform/store-state-in-azure-storage?tabs=azure-cli" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/developer/terraform/store-state-in-azure-storage?tabs=azure-cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Creating Azure Directory Service Principles: &lt;a href="https://learn.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest#az-ad-sp-create-for-rbac" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest#az-ad-sp-create-for-rbac&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Setting up Terraform: &lt;a href="https://developer.hashicorp.com/terraform/tutorials/azure-get-started/infrastructure-as-code" rel="noopener noreferrer"&gt;https://developer.hashicorp.com/terraform/tutorials/azure-get-started/infrastructure-as-code&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>azure</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Aks from the trenches - Why zone topology might not be the best solution</title>
      <dc:creator>Martin Humlund Clausen</dc:creator>
      <pubDate>Thu, 29 Jun 2023 09:11:27 +0000</pubDate>
      <link>https://dev.to/martinhc/aks-from-the-trenches-why-zone-topology-might-not-be-the-best-solution-2bb1</link>
      <guid>https://dev.to/martinhc/aks-from-the-trenches-why-zone-topology-might-not-be-the-best-solution-2bb1</guid>
      <description>&lt;p&gt;It is Friday around 4 pm, the sun is shining with the temperature hitting around 27 degrees Celsius outside and all your friends are leaving work to go to the beach. And just after the sprint demos are completed and you are about to leave the office, you get that feeling in your stomach where your subconscious has been brewing on a problem that was reported earlier to another team.&lt;/p&gt;

&lt;p&gt;Intermittent failure of requests for our customers, with no real availability pattern. You scramble the team for a late Friday afternoon in your effort to settle your stomach before hitting the door.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Azure Kubernetes Services without any challenges for more than a year
&lt;/h2&gt;

&lt;p&gt;Last year we decided to make the big move to a more robust service hosting platform, and coming from a single mother load of VM to handle the workload of around 22 different docker images, was a big relief. No more sleepless nights of what happens to that single VM goes down, yay!&lt;/p&gt;

&lt;p&gt;At the time our company had a huge focus on operational sustainability and reliability of services, and for that, Azure Kubernetes Services (AKS), had just the tool we needed: Zone-Affinity!&lt;/p&gt;

&lt;p&gt;So after spinning up a new AKS Cluster, we configured the node pool consisting of 3 nodes to be spread across Zone 1, Zone 2, and Zone 3 of our local region.&lt;/p&gt;

&lt;p&gt;For us to be highly available we wanted to have &lt;strong&gt;3&lt;/strong&gt; API services spread across all three zones and thus applied the following spec to our deployments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: ScheduleAnyway
        labelSelector:
          matchLabels:
            app: {{ $fullname }}
            version: {{ $imageTag }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup has been running for more than a year, and we have been happy… so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nodes running out of Memory
&lt;/h2&gt;

&lt;p&gt;You’ve scrambled your team together and just let that sinking feeling in your stomach out, and the team decides to have a look at the AKS instance…&lt;/p&gt;

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

&lt;p&gt;Dread sets in, at this point we’ve had around 9000 restarting pods, and it seems like Kubernetes does what it is supposed to do which is keep a quorum of running services and rescheduling pods all over our services node pool.&lt;/p&gt;

&lt;p&gt;After some additional investigations we saw that each of our nodes in the service node pull was been given the following taint&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[node.kubernetes.io/memory-pressure](http://node.kubernetes.io/memory-pressure):NoSchedule
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which basically means, dont schedule more pods on the &lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/#taint-based-evictions" rel="noopener noreferrer"&gt;node&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Additionally, we saw that the taint was only applied to Node 1 (Zone 1), and Node 3(Zone 3), so it made sense that since our topology said “spread 1 application on all three zones” and we’ve had schedule anyway set, we’ve constrained Kubernetes to try forever to schedule workload on Node 2 (Zone 2).&lt;/p&gt;

&lt;p&gt;Needless to say, customer tickets were starting to pop up left and right, and since we were unable to schedule the number of pods necessary to sustain throughput, we had to remedy the situation, so our customers would get happy again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Topology constraints, Yikes.
&lt;/h2&gt;

&lt;p&gt;The first option that came to mind was increasing the number of nodes that were available in the cluster to &lt;strong&gt;4&lt;/strong&gt;. And so we did..&lt;/p&gt;

&lt;p&gt;After we added the additional node to our cluster, we had one additional node In Zone 1 of the Azure region. Unbeknown to us at the time, we ran through our kubectl command for cleaning up the failed pods&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl delete pods --field-selector=status.phase==Failed -n umbraco-cloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secondly it was manually needed to remove the node taints, and so we did, by this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl taint nodes &amp;lt;nodename&amp;gt; node.kubernetes.io/memory-pressure:NoSchedule-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To our surprise AKS continued to re-taint our Nodes and kept trying to schedule workload onto Zone 2. At the time the team was a little bewildered, and it wasn’t until we had a look at our topology.&lt;/p&gt;

&lt;p&gt;And what we discovered was, &lt;strong&gt;oh well if it wasn’t the consequences of our own actions&lt;/strong&gt;…&lt;/p&gt;

&lt;p&gt;What we have discovered is that due to our topology constraints we forced services to be available in all three zones no matter what, and with no room for more pods on a specific node, aks would basically throw the towel in the ring.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we ended up doing
&lt;/h2&gt;

&lt;p&gt;In order for us to get to the beach with our friends, we ended up increasing the total node count to 6 in order to achieve equilibrium across all three zones in the cluster. As soon as we did that, Kubernetes automatically recovered, automatically removed the taints from the existing nodes, and automatically distributed the workload as expected.&lt;/p&gt;

&lt;p&gt;Of course, this cost us a little extra, but the alternative was first to bump our cluster down to 4 nodes, change our helm charts to relax the topology, and then re-release all our services. But doing that on a Friday at 17 PM would have everybody miss out on the beach trip, and I would rather have 6 nodes during the weekend and not worry about it. A job for the future Platform Team…&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing remarks
&lt;/h2&gt;

&lt;p&gt;This ended up being quite a good team-bonding experience, and one of those issues where quite fun to solve. Additionally, I do not believe that this issue was something we could not foresee when we introduced Aks. It is just one of those things, you’ve to learn the hard way of operating Kubernetes.&lt;/p&gt;

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