<?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>Forem: Christian Lechner</title>
    <description>The latest articles on Forem by Christian Lechner (@lechnerc77).</description>
    <link>https://forem.com/lechnerc77</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%2F402478%2F29071667-8c92-403d-8eb0-2ec9a9210ec1.png</url>
      <title>Forem: Christian Lechner</title>
      <link>https://forem.com/lechnerc77</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/lechnerc77"/>
    <language>en</language>
    <item>
      <title>Exploring Terramate Cloud part 2 – A walkthrough</title>
      <dc:creator>Christian Lechner</dc:creator>
      <pubDate>Wed, 30 Jul 2025 05:39:41 +0000</pubDate>
      <link>https://forem.com/lechnerc77/exploring-terramate-cloud-part-2-a-walkthrough-5a6i</link>
      <guid>https://forem.com/lechnerc77/exploring-terramate-cloud-part-2-a-walkthrough-5a6i</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/lechnerc77/exploring-terramate-cloud-part-1-getting-things-setup-5h0f"&gt;first part&lt;/a&gt; of this two-part blog post we covered the basic setup to use Terramate Cloud. In this second part we take a closer look at the Terramate Cloud offering &lt;em&gt;per se&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Let us jump right in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terrmate Cloud
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://terramate.io/" rel="noopener noreferrer"&gt;Terramate Cloud&lt;/a&gt; is a SaaS offering that complements the Terramate CLI with features like monitoring of your stacks. &lt;/p&gt;

&lt;p&gt;As we already onboarded three stacks we explore the different sections that are offered by Terramate Cloud and test the setup for configuration drifts and changes in the deployments. &lt;/p&gt;

&lt;h3&gt;
  
  
  Your "Homework"
&lt;/h3&gt;

&lt;p&gt;The first thing you see once logged into Terramate Cloud is the “Your ‘Homework’” section that gives you a crisp overview about your setup and its state:&lt;/p&gt;

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

&lt;p&gt;Usually when exploring new software, you first must get used to the wording and the semantics that the solution introduces. That should be covered in the documentation, but that also means you must switch between tabs/windows etc. &lt;/p&gt;

&lt;p&gt;But do you see that button in the right upper corner labeled “Explain this page”? Pressing this button adds extensive in-place help and explanations to the page and its sections: &lt;/p&gt;

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

&lt;p&gt;That’s cool. I really like this as it helps new joiners right in the place where they need it. Unfortunately, this is the only page where this in-place help is available. &lt;/p&gt;

&lt;p&gt;While I do not think that this is a fitting approach for every page or detail section in Terramate Cloud, I would e.g. like to also have that in the Dashboard page. Overall a nice start.&lt;/p&gt;

&lt;p&gt;Let’s move on to the “Dashboard” section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dashboard
&lt;/h3&gt;

&lt;p&gt;As the name implies this page gives an overview about the metrics of your setup. Besides some KPIs about the stacks the main thing is the display of &lt;a href="https://dora.dev/" rel="noopener noreferrer"&gt;DORA metrics&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;According to the pricing overview this is not included in the free edition, but I see them and I am not sad about that:&lt;/p&gt;

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

&lt;p&gt;Due to the limited setup in my experiments the numbers are not impressive (though I am elite in some areas 🤪).&lt;/p&gt;

&lt;p&gt;I think the DORA metrics part is a very valuable addition to the offering. While developing and providing business apps clearly show the impact on business, the management of infrastructure is often not seen by e.g., C-level as an essential building block for this although it is. Having these metrics in place &lt;em&gt;out-of-the-box&lt;/em&gt; will help the teams when arguing about the importance of a decent IaC setup and of course also help the team improve. This will also have a positive impact.&lt;/p&gt;

&lt;p&gt;The only thing that was a bit irritating about the metrics was that the preconfigured time interval points to the previous month. I would have expected that it points to the current month. Maybe looking at a “completed” month is more reasonable for overall reporting, I would always like to see the current state.&lt;/p&gt;

&lt;p&gt;Anyway, this feature is imho a highlight of Terramate Cloud.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;p&gt;The next section covers the deployed “Resources”. As the name states this section provides an overview of all resources and their state:&lt;/p&gt;

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

&lt;p&gt;There are tons of sorting options, so tailoring the view to your needs is possible.&lt;/p&gt;

&lt;p&gt;However, there are some small points on the overview that came to my attention (keeping in mind that I am using a “exotic” setup on SAP BTP):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The column “Account” is empty. I have no idea what this refers to. As there is no entry for any resource, it could also be hidden automatically by the platform. But this is not really an issue.&lt;/li&gt;
&lt;li&gt;The column “Name” seems to be fetched from the resource if it has a field called “name”. If there is no such field, it remains empty. Although in my case, this is most of the time empty, it is useful for me. &lt;/li&gt;
&lt;li&gt;The column provider triggered my inner “Monk” a bit:  the first letter is capitalized, and the organization of the provider is missing. I don't know how this is derived, but I would like to see “SAP/btp” there as defined in the &lt;code&gt;provider.tf&lt;/code&gt; file or in the Hashicorp Terraform/OpenTofu registry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the overview, you can also dive into the details of each resource:&lt;/p&gt;

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

&lt;p&gt;The details also contain the state, and I assume that sensitive data might be displayed, but I did not test that. The detailed view also provides a cross-navigation to the Terramate stack the resource belongs to.&lt;/p&gt;

&lt;p&gt;I think this is a useful overview/detail page combination enabling you to get a quick access to the basic information of the resources that are deployed.&lt;/p&gt;

&lt;p&gt;As we have a cross-navigation to stacks, let’s take a closer look at that section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stacks
&lt;/h3&gt;

&lt;p&gt;The “Stacks” section provides an overview of all stacks available to Terramate Cloud:&lt;/p&gt;

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

&lt;p&gt;Each line of the overview contains some basic stack information. From there you can navigate to the details screen for each stack:&lt;/p&gt;

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

&lt;p&gt;Here we find more stack-related information like deployments or drift runs or policy checks (that are not available for the free edition). &lt;/p&gt;

&lt;p&gt;From this view we can navigate to the details per resource which closes the loop to the “Resources” section.&lt;/p&gt;

&lt;p&gt;As an intermediate summary until: nice experience, reasonable structuring of the views and detail views, the displayed information and the cross-navigation.&lt;/p&gt;

&lt;p&gt;From the short evaluation I would say that for basic overviews my preferred areas are the &lt;em&gt;Dashboard&lt;/em&gt; with the DORA metrics and the &lt;em&gt;stacks&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Now let's evaluate the dynamical aspects of Terramate Cloud and by this investigate the remaining sections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling of Configuration Drift
&lt;/h3&gt;

&lt;p&gt;Let’s introduce a drift. For that I manually removed some labels on one SAP BTP subaccount and triggered the GitHub Actions workflow for drift detection (remember: no auto remediation in place).&lt;/p&gt;

&lt;p&gt;Once the drift detection was executed, we can immediately see the effect in the “Dashboard” section namely that one stack is unhealthy due to drift:&lt;/p&gt;

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

&lt;p&gt;Same is true for the “Stacks “ where we also can identify the stack that causes the issue:&lt;/p&gt;

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

&lt;p&gt;This is also a scenario when the “Alerts” section comes into play:&lt;/p&gt;

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

&lt;p&gt;This section informs us about the issue. As you can see in the screenshot there are also some useful predefined filters in place as buttons, I like that. &lt;/p&gt;

&lt;p&gt;Drilling into the details of the alarm reveals the details of the drift namely the result of the planning:&lt;/p&gt;

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

&lt;p&gt;This already helps a lot to pin down the issue and trigger the consequent actions if you know Terraform and the semantics of the provider that is used.&lt;/p&gt;

&lt;p&gt;From several discussions with customers, I know that this result of the &lt;code&gt;terraform plan&lt;/code&gt; can cause some uncertainty and questions to the fact that there are also in-place updates. They are all on computed fields and therefore not an issue, but they are often the source of confusion. &lt;/p&gt;

&lt;p&gt;And this is where I like to see the new &lt;em&gt;AI explain&lt;/em&gt; feature in Terramate Cloud. My opinion: one of the right spots for using AI in contrast to all the fancy marketing promises floating around at the moment. &lt;/p&gt;

&lt;p&gt;What is even better: this feature is also available in the free plan. So let's try it out and press the “Explain drift with AI” button. This is what we get:&lt;/p&gt;

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

&lt;p&gt;I like that explanation and I think this is really useful. I understand this cannot be extrapolated to complex setups/drifts. &lt;/p&gt;

&lt;p&gt;However, imho if you are facing very complex drifts, I think you have another issue that has nothing to do with IaC: either the platform you are using per se behaves "weird" or (more often the case) your organizational setup and processes are far from where they should be when it comes to IaC and you blindly rely on “IaC will remediate things anyway”. &lt;/p&gt;

&lt;p&gt;After fixing the drift manually and redoing the drift run via GitHub Actions, all turned green again in the Terramate Cloud cockpit. This includes the alert that gets the status „fixed“ &lt;em&gt;automagically&lt;/em&gt;:&lt;/p&gt;

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

&lt;p&gt;One thing to be aware: no matter if there is a drift or not, the GitHub Action workflow based on the template will show a successful execution. The fair assumption is that you rely on Terramate Cloud and not on monitoring the GitHub Action workflow status when it comes to drift detection, but something to be aware of.&lt;/p&gt;

&lt;p&gt;There is one small bug I recognized in that context: if the alert is not assigned to a user and gets fixed you can no longer assign a user to it. If you try to do an assignment you get an error message/pop-up that states „Conflict“:&lt;/p&gt;

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

&lt;p&gt;Small issue, but I think this should be possible even when a issue is fixed.&lt;/p&gt;

&lt;p&gt;I did not dive into the depths of organizational workflows on how this alerting needs to be integrated into the overall organizational processes like adding an issue in GitHub or alike and referencing the alert there. I at least did not see a way to reference the alert somewhere else.&lt;/p&gt;

&lt;p&gt;Let’s move on and make a change to the configuration via a pull request (PR).&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling of Configuration Changes
&lt;/h3&gt;

&lt;p&gt;Let us change the security setting of our subaccounts for all stacks. This is a dedicated resource, so we should see three updates of the resource, one for each stack.&lt;/p&gt;

&lt;p&gt;I created a PR with the change which triggers the preview workflow in the repository and the PR gets synched to Terramate Cloud. As the repository is connected to Terramate Cloud via the Terramate GitHub app, the PR gets a comment of what will happen when getting applied:&lt;/p&gt;

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

&lt;p&gt;The comment also contains a link to Terramate Cloud namely to the details view of the PR. &lt;/p&gt;

&lt;p&gt;First, this connection is great (you do not need to manually navigate to Terramate Cloud) but also that it brings you to the details view. Yes, it is obvious that this is needed, but I know a lot of solutions that would bring you to the overview page and you must navigate on your own. &lt;/p&gt;

&lt;p&gt;Let's do it the manual way and open the "Pull Request" section. We see the PR and some basic information:&lt;/p&gt;

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

&lt;p&gt;At a first glance we see how many resources and stacks are affected by the change. Drilling into the details give us more information about the change of the infrastructure per stack:&lt;/p&gt;

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

&lt;p&gt;Some more details including the logs are also available:&lt;/p&gt;

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

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

&lt;p&gt;You can also display the information in an ASCII view. This might be useful when you think that there is a rendering issue, but other than that I wouldn’t know when to use this ASCII view.&lt;/p&gt;

&lt;p&gt;As for drifts explaining the plan is a good use case for AI. This time it is not a button you need to press but a dedicated tab in the details view of the PR:&lt;/p&gt;

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

&lt;p&gt;The explanation is good and contains all relevant information about the change. I like that. As for the drift this was a small change, so this feature will probably really shine in more complex update scenarios.&lt;/p&gt;

&lt;p&gt;While I like the feature &lt;em&gt;per se&lt;/em&gt;, I think it would make sense to integrate the AI companion or copilot or however you want to call it, consistently in the different screens.  &lt;/p&gt;

&lt;p&gt;As we are happy with the change let’s merge the PR. This results in a new deployment as we can see in the corresponding section "Deployments" in Terramate Cloud:&lt;/p&gt;

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

&lt;p&gt;As in the other screens we can directly navigate to the details:&lt;/p&gt;

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

&lt;p&gt;Drilling down further is possible, so that we can analyze the deployment details per stack: &lt;/p&gt;

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

&lt;p&gt;Here we have the AI feature again, this time as a button. I am also not sure if it is really needed here as this change already happened and I do not see value in getting an explanation &lt;em&gt;after&lt;/em&gt; a successful deployment. If something goes sideways, this might make more sense, but then we see an alert.&lt;/p&gt;

&lt;p&gt;Overall, the experience in Terramate Cloud for deploying changes is also quite nice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;I already liked Terramate CLI and its concepts so what is my summary about Terramate Cloud? &lt;/p&gt;

&lt;p&gt;Terramate Cloud complements that with several nice features supporting the operations namely the monitoring and troubleshooting part of IaC. The onboarding is smooth, and the experience is very good. The interplay with established GitHub flows works as expected. Function-wise the DORA dashboard as well as the alerting and PR handling look really good. &lt;/p&gt;

&lt;p&gt;From the UI perspective I like the clean style and having the right information in the right places.  Although I did not work with the tool for weeks, I enjoyed the connection between the views: depending on your way of work or the preferred view to start from I would make the bold statement that you always see the right information or can drill down/navigate to it.&lt;/p&gt;

&lt;p&gt;Performance-wise the UI is fast as one would expect but does not always get in such solutions. &lt;/p&gt;

&lt;p&gt;The recently added AI features are at the spots where I would expect them and where I see added value. Not overhyped, agentic, MCP, &lt;em&gt;fill in the blanks&lt;/em&gt; BS, but fitting into the flow and nicely integrated into the UI (except for some small inconsistencies).&lt;/p&gt;

&lt;p&gt;There are some things that I missed during my walk through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storing settings and filters for views: as everybody has his own way of working and preferred setups of overviews it might be useful to be able to store some personal settings in that place. I don’t know if that’s just me being already &lt;em&gt;SAP-ified&lt;/em&gt; to a large degree or if other see this as a useful feature, too.&lt;/li&gt;
&lt;li&gt;Most commonly used CI/CD pipelines are covered that I also know from customers. However, I am missing more content for Azure DevOps that I also see being used at a lot of customers. Maybe there was not yet demand for that from Terramate customers.&lt;/li&gt;
&lt;li&gt;After finishing my experiments, I wanted to clean up the setup on Terramate Cloud. Basically, I was looking for a “Delete/Offboard stacks” button or a CLI command like &lt;code&gt;terramate rm stack xyz -- sync-deployment&lt;/code&gt;. I did not find something like that, so I asked on the Terramate Discord. I want to stress again, I used the free version, I am not a paying customer. &lt;strong&gt;But&lt;/strong&gt; I got the answer in a few hours - highly appreciated and something I experience with every question to the Terramate team. I was right, this functionality does not exist, only archiving is possible (or asking the Terramate team to manually delete stuff). I think this would be a handy feature especially when doing proof-of-concepts on the platform. &lt;/li&gt;
&lt;li&gt;One more conceptual question came to my mind about best practices on how to structure Terramate Cloud organizations in combination with GitHub organizations and repositories to enable a smooth integration. I am sure there is not one perfect setup, but some guidance about the pros and cons on different ways/common scenarios would be appreciated. The “easiest” way is of course to have everything under one GitHub org, but I think then Terramate Cloud overviews become quite crowded and there are maybe other drawbacks. It would be really interesting to learn what the experiences and good practices of the Terramate team are in that area.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think all those points are already nitpicking to some degree. Terramate Cloud is as it is already a very useful tool and complements the Terramate CLI at the right areas. &lt;/p&gt;

&lt;p&gt;I did not wrap my head about integrating Terramate Cloud in your existing workflows, like i shortly mentioned when talking about drifts and alerts. Something that would need further investigation that I did not do, but would certainly be aprt of the evaluation when you are doing a proof-of-concept and evaluate Terramate Cloud for your organization  &lt;/p&gt;

&lt;p&gt;Long story short: If you are looking for a solution in the IaC area for making life easier in operations and monitoring Terramate Cloud is worth a closer look. &lt;/p&gt;

</description>
      <category>terraform</category>
      <category>terramate</category>
      <category>sap</category>
      <category>sapbtp</category>
    </item>
    <item>
      <title>Exploring Terramate Cloud part 1 – Getting things set up</title>
      <dc:creator>Christian Lechner</dc:creator>
      <pubDate>Wed, 30 Jul 2025 05:39:39 +0000</pubDate>
      <link>https://forem.com/lechnerc77/exploring-terramate-cloud-part-1-getting-things-setup-5h0f</link>
      <guid>https://forem.com/lechnerc77/exploring-terramate-cloud-part-1-getting-things-setup-5h0f</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Exploring Terramate &lt;em&gt;Cloud&lt;/em&gt; was on my to do list for a while now. The latest updates of Terramate Cloud about DORA metrics and integration of some AI explain features reminded me of that. A good time to get this thing done.&lt;/p&gt;

&lt;p&gt;This is a two parted blog post. The first part you are currently reading is about getting the basic setup for using Terramate Cloud in place. The second part is about how Terramate Cloud itself and what it brings to the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;I have already written some blog posts about the Terramate &lt;em&gt;CLI&lt;/em&gt; and how it can support you with challenges in your day-to-day work with Infrastructure as Code (IaC), in my case Terraform/OpenTofu. &lt;/p&gt;

&lt;p&gt;The CLI is open source and free of charge. In my opinion it is a very useful tool with some smart concepts and can help you with some challenges around setup of infrastructure especially on SAP BTP. If you are interested in those blog posts, here they are: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/lechnerc77/terramate-this-sap-btp-5a8p"&gt;Terramate this SAP BTP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/lechnerc77/experimenting-with-terramate-and-sap-btp-22m1"&gt;Experimenting with Terramate and SAP BTP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there is also a SaaS offering of Terramate, namely &lt;a href="https://terramate.io/" rel="noopener noreferrer"&gt;Terramate Cloud&lt;/a&gt;. This offering focuses on the management and observability part of IaC. &lt;/p&gt;

&lt;p&gt;There is a free plan for Terramate Cloud which reduces the barrier of trying it out to zero.&lt;/p&gt;

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

&lt;p&gt;The basic prerequisite is to have a IaC setup in place that consists of Terramate &lt;a href="https://terramate.io/docs/cli/stacks/" rel="noopener noreferrer"&gt;stacks&lt;/a&gt;. I created such a setup and you can find it on GitHub: &lt;a href="https://github.com/btp-automation-scenarios/btp-terramate-cloud" rel="noopener noreferrer"&gt;https://github.com/btp-automation-scenarios/btp-terramate-cloud&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;It comprises three stacks (dev, test and prod) with a basic setup on SAP BTP. I used the Terramate code generation feature, but that is not needed to use Terramate Cloud. The only prerequisite is to have Terramate stacks in place.&lt;/p&gt;

&lt;p&gt;I deployed the stacks from my local machine via the Terramate CLI. My remote state storage is an Azure Blob Storage, but that has no impact on Terramate Cloud.&lt;/p&gt;

&lt;p&gt;On the Terramate Cloud side we first need to sign up to Terramate Cloud and create an organization that is used to connect your deployments to Terramate Cloud. No credit card required, which I highly appreciate when trying things out.&lt;/p&gt;

&lt;p&gt;With these two things in place we must make Terramate Cloud aware of our deployed stacks. The onboarding process is very well described in the documentation and consists of three steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;em&gt;Terramate-specific configuration&lt;/em&gt; must be added to your repository that points to the organization we created in Terramate Cloud as described here: &lt;a href="https://terramate.io/docs/cli/on-boarding/terraform#_5-configure-your-repository" rel="noopener noreferrer"&gt;https://terramate.io/docs/cli/on-boarding/terraform#_5-configure-your-repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;We must log into Terramate Cloud via the Terramate CLI as described here: &lt;a href="https://terramate.io/docs/cli/on-boarding/terraform#_6-login-from-cli" rel="noopener noreferrer"&gt;https://terramate.io/docs/cli/on-boarding/terraform#_6-login-from-cli&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;And as a last step we must trigger an initial sync of our setup to Terramate Cloud. The easiest option is to trigger a drift detection which triggers the sync. As for the previous steps the documentation explains how to do that: &lt;a href="https://terramate.io/docs/cli/on-boarding/terraform#_7-sync-stacks-to-terramate-cloud" rel="noopener noreferrer"&gt;https://terramate.io/docs/cli/on-boarding/terraform#_7-sync-stacks-to-terramate-cloud&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it. Now the stacks are onboarded to Terramate Cloud. &lt;/p&gt;

&lt;p&gt;As we also want to monitor our stacks, we need to define CI/CD workflows to keep Terramate Cloud informed about changes but also get information back e.g, in the case of pull requests. &lt;/p&gt;

&lt;p&gt;As my code is on GitHub, I made the necessary configuration to give Terramate access to my GitHub repository. This comprises the enabling of OIDC and adding a Terramate GitHub app to my GitHub repository. While I did that on repository level, you can also do that on GitHub organization level.  &lt;/p&gt;

&lt;p&gt;To have an interaction with Terramate I added three workflows to my repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A workflow for the &lt;em&gt;deployment&lt;/em&gt; of the stacks after a PR got merged&lt;/li&gt;
&lt;li&gt;A workflow for &lt;em&gt;drift detection&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;A workflow for executing a &lt;em&gt;preliminary planning&lt;/em&gt; of changes that gets triggered whenever a pull request is opened.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setting up the workflows is also quite easy as the Terramate documentation provides templates for the three workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://terramate.io/docs/cli/automation/github-actions/preview-workflow" rel="noopener noreferrer"&gt;PR preview workflow&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://terramate.io/docs/cli/automation/github-actions/deployment-workflow" rel="noopener noreferrer"&gt;deployment workflow&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;The &lt;a href="https://terramate.io/docs/cli/automation/github-actions/drift-check-workflow" rel="noopener noreferrer"&gt;drift check workflow&lt;/a&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only adjustment I made was that I removed the automatic remediation of the drift in the drift detection workflow.&lt;/p&gt;

&lt;p&gt;Taking a closer look at the workflow code, you will see that the change detection of Terramate CLI comes into play (&lt;em&gt;no we do not use &lt;code&gt;-X&lt;/code&gt;&lt;/em&gt;) as well as some Terramate Cloud specific options that are responsible for the synchronization:&lt;/p&gt;

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

&lt;p&gt;There was only one thing I struggled with when using the proposed actions namely the conditional check for changes in the stacks. This is used to execute the action only if there have been changes in the stacks which is proposed like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List changed stacks&lt;/span&gt;
        &lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;list&lt;/span&gt;
        &lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terramate list –changed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The condition for consequnt steps is defined as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.list.outputs.stdout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This did not work for me, and I also did not find &lt;code&gt;stdout&lt;/code&gt; as a default output for a GitHub Actions workflow step. Maybe I missed something or I made a typo I could not identify, but to get things working I replaced the proposed code with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List changed stacks&lt;/span&gt;
        &lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;list&lt;/span&gt;
        &lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;terramate list --changed &amp;gt; changed-stacks.txt&lt;/span&gt;
          &lt;span class="s"&gt;echo "CHANGEDSTACKS=$(awk 'END { print NR }' changed-stacks.txt)" &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;
          &lt;span class="s"&gt;rm -rf changed-stacks.txt &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not elegant, but pushing the &lt;code&gt;terramate list&lt;/code&gt; command output directly to the GitHUb output gave me some errors in the consequent execution.&lt;/p&gt;

&lt;p&gt;The condition then reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.list.outputs.CHANGEDSTACKS &amp;gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with that our setup is ready for exploring Terramate Cloud. &lt;/p&gt;

&lt;p&gt;Overall, the onboarding experience is straightforward and there is no obstacle to try this offering out, at least none that I came across. The documentation is helpful especially when it comes to the templates for the pipelines which are a huge time saver. By the way: these templates also exist for GitLab and Bitbucket.  &lt;/p&gt;

&lt;p&gt;This onboarding experience raised the barrier quite a lot, let’s see if Terramate Cloud &lt;em&gt;per se&lt;/em&gt; can keep up with it in the next blog post &lt;a href="https://dev.to/lechnerc77/exploring-terramate-cloud-part-2-a-walkthrough-5a6i"&gt;Exploring Terramate Cloud part 2 – A walkthrough&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>terramate</category>
      <category>sap</category>
      <category>sapbtp</category>
    </item>
    <item>
      <title>Test Drive the Terraform MCP Server with GitHub Copilot Chat</title>
      <dc:creator>Christian Lechner</dc:creator>
      <pubDate>Thu, 22 May 2025 08:05:41 +0000</pubDate>
      <link>https://forem.com/lechnerc77/test-drive-the-terraform-mcp-server-with-github-copilot-chat-3jf1</link>
      <guid>https://forem.com/lechnerc77/test-drive-the-terraform-mcp-server-with-github-copilot-chat-3jf1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hashicorp recently released a first version of an MCP server for Terraform that was announced at &lt;a href="https://www.hashicorp.com/en/blog/hashicorp-microsoft-build-2025-automate-secure-scale-on-azure" rel="noopener noreferrer"&gt;Microsoft Build&lt;/a&gt;. This allows us to enrich the experience with GitHub Copilot Chat when it comes to Terraform.&lt;br&gt;
Currently the focus is on querying the Terraform Registry for artifact information and request recommendations.&lt;/p&gt;

&lt;p&gt;In this blog post I want to guide you through the setup which has some pitfalls and show you some first experiences with the MCP server.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;To start our journey with the Terraform MCP server we must have some prerequisites in place namely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker is installed and running&lt;/li&gt;
&lt;li&gt;VS Code is installed and GitHub Copilot is set up. Make sure that you are running the latest stable version. The last few versions of VS Code came with some improvements and bug fixes when it comes to GitHub Copilot Chat and the integration with MCP servers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If these prerequisites are in place we will first download the Docker image of the MCP server from Docker Hub via the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull hashicorp/terraform-mcp-server:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's switch to VS Code and set up the configuration needed for the MCP server. I struggled a bit with the setup, and I got the impression that the documentation is a bit unclear, but maybe it is only me. Let's go through the steps together.&lt;/p&gt;

&lt;p&gt;First, we must make sure that the settings in VS Code are matching the requirements for the MCP setup. There are two things that you need to check:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the settings of VS Code make sure that the Agent mode for GitHub Copilot Chat is enabled:&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Next, we must make sure that the MCP server is enabled:&lt;/p&gt;

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

&lt;p&gt;Next, we want to make GH Copilot aware that there is a MCP server available. There are different options available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Workspace setting - configuration for the current workspace&lt;/li&gt;
&lt;li&gt;User settings - configuration for all workspaces&lt;/li&gt;
&lt;li&gt;Autmatic discovery - this enables a auto discovery of an MCP server exposed by tools like Claude Desktop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will do the setup for one VS Code workspace (and do not mix it up with Terraform workspaces).&lt;/p&gt;

&lt;p&gt;To do so, we create a new folder called &lt;code&gt;test-terraform-mcp&lt;/code&gt;. Inside of this folder we create another folder called &lt;code&gt;.vscode&lt;/code&gt;&lt;br&gt;
Inside of the &lt;code&gt;.vscode&lt;/code&gt; folder we create an empty file called &lt;code&gt;mcp.json&lt;/code&gt;. We open the empty JSON file in VS Code:&lt;/p&gt;

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

&lt;p&gt;There is a blue button in the lower right corner that says, "Add Server ...". &lt;strong&gt;Do not&lt;/strong&gt; use this button. This opens some guided procedure that didn't help me at all and also behaves different than documented. We will paste the required configuration directly into the file. The configuration looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"terraform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--rm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"hashicorp/terraform-mcp-server"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we save the file and wait a bit. Now we see a tiny &lt;code&gt;start&lt;/code&gt; link in the line below &lt;code&gt;"servers"&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;We press the &lt;code&gt;start&lt;/code&gt; link to start the MCP server as defined in the &lt;code&gt;mcp.json&lt;/code&gt; file. This will start the Docker container. This also changes the available links displayed in the &lt;code&gt;mcp.json&lt;/code&gt; file:&lt;/p&gt;

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

&lt;p&gt;Let's do another check that GitHub Copilot is aware of the MCP server. We open GitHub Copilot Chat and make sure that we are in the Agent mode:&lt;/p&gt;

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

&lt;p&gt;Now we click on the tool icon in the Copilot Chat and check if the MCP server is available:&lt;/p&gt;

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

&lt;p&gt;This opens a list in the command palette of VS Code showing all available MCP servers and their toolsets. The result should look like this:&lt;/p&gt;

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

&lt;p&gt;Okay we are ready to go. The MCP server is running, and GitHub Copilot Chat is aware of it. Now we can start asking questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Test Drive
&lt;/h2&gt;

&lt;p&gt;Time to challenge the MCP server with some questions. My home turf is the Terraform provider for SAP BTP. Let's see if the MCP server can help us with some questions around the provider.&lt;/p&gt;

&lt;p&gt;Let's start with a question about entitlements and what resources are available in the provider:&lt;/p&gt;

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

&lt;p&gt;What I learned quickly is that you must point the MCP server to the right namespace and provider name. Names like "Terraform provider for SAP BTP" do not work well. GitHub Copilot might be able to finally find the provider, but some steps are needed in the agentic execution to get on the right track.&lt;/p&gt;

&lt;p&gt;The agent will execute serveral steps using the MCP server toolsets to get to the result. Everything is transparent for the user which is nice:&lt;/p&gt;

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

&lt;p&gt;And the result looks good:&lt;/p&gt;

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

&lt;p&gt;That's what we expected. Let's go a bit deeper and ask for the details of the entitlements on subaccount level:&lt;/p&gt;

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

&lt;p&gt;Yes that seems to work fine. Let's switch gears and test the other toolsets about modules:&lt;/p&gt;

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

&lt;p&gt;The result is this:&lt;/p&gt;

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

&lt;p&gt;Hmmm ... that's not exactly what we expected. Let's cross check the Terraform Registry to see if there are more modules available:&lt;/p&gt;

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

&lt;p&gt;Okay let's give Copilot another chance and ask if there aren't any other modules available:&lt;/p&gt;

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

&lt;p&gt;It stays confident with the (incomplete) answer. Then let's ask directly for the missing module:&lt;/p&gt;

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

&lt;p&gt;Okay, now it found the module. I have no idea why it missed it as the metadata in the registry seems to connect the dots correctly, but maybe the search query from the toolset is restricting the results to "something with BTP" which is not visible in the module itself. Interesting to see that the metadata doesn't seem to be a part of query.&lt;/p&gt;

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

&lt;p&gt;The MCP server for Terraform adds some nice features to the GitHub Copilot Chat experience. I see some value when starting with Terraform on SAP BTP and have such a integrated experience. Having said that for experienced users I see only limited value (despite of trying it like I did). To be fair, the MCP server for terraform is still in a very early stage (it's beta and version 0.1.0) so I am curious to see how it will evolve in the future. I will keep an eye on it.&lt;/p&gt;

&lt;p&gt;When it comes to the setup. I think this could be a bit smoother with more details in the documentation, but overall it is not too complicated, and things are moving fast when it comes to VS Code and GitHub Copilot. I am quite sure that the setup will be easier in the future.&lt;/p&gt;

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

&lt;p&gt;Here are some references for more information about the Terraform MCP server and MCP servers in VS Code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/docs/tools/mcp-server" rel="noopener noreferrer"&gt;https://developer.hashicorp.com/terraform/docs/tools/mcp-server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hashicorp/terraform-mcp-server" rel="noopener noreferrer"&gt;https://github.com/hashicorp/terraform-mcp-server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/docs/copilot/chat/mcp-servers" rel="noopener noreferrer"&gt;https://code.visualstudio.com/docs/copilot/chat/mcp-servers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>mcp</category>
      <category>githubcopilot</category>
      <category>sap</category>
    </item>
    <item>
      <title>Automating an Open Source Project with GitHub Actions</title>
      <dc:creator>Christian Lechner</dc:creator>
      <pubDate>Tue, 25 Feb 2025 13:56:33 +0000</pubDate>
      <link>https://forem.com/lechnerc77/automating-an-open-source-project-with-github-actions-l0e</link>
      <guid>https://forem.com/lechnerc77/automating-an-open-source-project-with-github-actions-l0e</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you are working on GitHub and maybe even maintain an open-source project, GitHub Actions are an excellent tool to automate tasks going far beyond "typical" CI/CD automation.&lt;/p&gt;

&lt;p&gt;In this blog post I describe the GitHub Action setup that we are running in open-source projects in the context of developing a tool for working with Terraform/OpenTofu namely the import of existing setups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Parameters
&lt;/h2&gt;

&lt;p&gt;In this blog post we will focus on the GitHub repository &lt;a href="https://github.com/SAP/terraform-exporter-btp" rel="noopener noreferrer"&gt;https://github.com/SAP/terraform-exporter-btp&lt;/a&gt; and the setup therein. The project is an open-source project and contains a CLI that is written in Golang.&lt;/p&gt;

&lt;p&gt;The CLI handles some Terraform task around the import of existing resources, so it also contains some Terraform configurations in the Hashicorp Configuration Language (HCL) mainly dealing with the setup for integration tests.&lt;/p&gt;

&lt;p&gt;All changes to the repository must be made via pull requests. This is safeguarded by protecting the main branch via a &lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets" rel="noopener noreferrer"&gt;ruleset&lt;/a&gt;. The configuration is part of the repository settings.&lt;/p&gt;

&lt;p&gt;The CLI is published as a release using &lt;a href="https://goreleaser.com/" rel="noopener noreferrer"&gt;GoReleaser&lt;/a&gt; to build the release.&lt;/p&gt;

&lt;p&gt;All interaction with the community is happening via the repository namely via GitHub issues and GitHub Discussions including milestones that are part of the issues.&lt;/p&gt;

&lt;p&gt;From a development project perspective i.e., for managing the backlog, we are using GitHub Projects. We work on one central project that contains the issues of all our Terraform specific GitHub repositories.&lt;/p&gt;

&lt;p&gt;The documentation of the CLI is also part of the repository and published to a GitHub page of the repository.&lt;/p&gt;

&lt;p&gt;As we are part of an organization that belongs to a corporate, two more requirements come into play:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The repository should be attached to Sonar Cloud to have a standardized way of monitoring code quality&lt;/li&gt;
&lt;li&gt;The security vulnerability reporting is managed via a central approach of the organization. Hence, we have not activated GitHubs's &lt;a href="https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability" rel="noopener noreferrer"&gt;private vulnerability reporting&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Our Goal
&lt;/h2&gt;

&lt;p&gt;Based on these parameters and requirements, we wanted to automate as many repetitive tasks that we face in our daily work and keep the overall quality of the repository and its contents as high as possible. &lt;/p&gt;

&lt;p&gt;In addition, we wanted to make the life of the users that file issues or contribute code as easy as possible.&lt;/p&gt;

&lt;p&gt;This comprises of course the code &lt;em&gt;per se&lt;/em&gt; but also more generic tasks like handling of issues and PRs.&lt;/p&gt;

&lt;p&gt;As we are on GitHub, it is a natural choice to use &lt;em&gt;GitHub&lt;/em&gt; Actions for this goal. Let's see what we automated, how we did that and what to consider.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Action Areas
&lt;/h2&gt;

&lt;p&gt;We use GitHub Actions (or other automation capabilities of GitHub) in different areas. We will discuss the different areas in dedicated sections.&lt;/p&gt;

&lt;p&gt;We will cover the following topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic project hygiene&lt;/li&gt;
&lt;li&gt;Issues&lt;/li&gt;
&lt;li&gt;Pull request&lt;/li&gt;
&lt;li&gt;CI tasks esp. testing&lt;/li&gt;
&lt;li&gt;Integration Tests&lt;/li&gt;
&lt;li&gt;Releases&lt;/li&gt;
&lt;li&gt;Documentation Generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let us walk through the different areas and learn what approach we took.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic project hygiene
&lt;/h3&gt;

&lt;p&gt;Some basic functionality is always used when we work on GitHub namely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/dependabot" rel="noopener noreferrer"&gt;Dependabot&lt;/a&gt; to keep your projects up to date. We configured this via &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/dependabot.yml" rel="noopener noreferrer"&gt;&lt;code&gt;dependabot.yml&lt;/code&gt;&lt;/a&gt; to cover the ecosystems used in our project like Golang or GitHub Actions.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codeql.github.com/" rel="noopener noreferrer"&gt;CodeQL&lt;/a&gt;, for vulnerability scanning of the code. We use a configuration defined in a &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/workflows/codeql.yml" rel="noopener noreferrer"&gt;&lt;code&gt;codeql.yml&lt;/code&gt;&lt;/a&gt; file. CodeQL is triggered upon pull requests, pushes to the main branch as well as periodically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also have switched on &lt;a href="https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning" rel="noopener noreferrer"&gt;&lt;em&gt;Secret Scanning&lt;/em&gt;&lt;/a&gt; for generic secrets. This configuration is part of the repository settings and available under the &lt;em&gt;Code Security&lt;/em&gt; settings.&lt;/p&gt;

&lt;p&gt;As we have markdown files in our repository that contain links to places in the repository or to further external documentation we want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;have spell checking of the markdown files&lt;/li&gt;
&lt;li&gt;have a periodic checking of the links because what is more annoying than clicking on a link and then getting a 404.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both requirements are covered by GitHub Actions.&lt;/p&gt;

&lt;p&gt;Checking the links that are part of the repository is handled by a periodically executed &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/workflows/links-watcher-cron.yml" rel="noopener noreferrer"&gt;workflow&lt;/a&gt;. The core of the check is the &lt;a href="https://github.com/lycheeverse/lychee-action" rel="noopener noreferrer"&gt;lychee link checking action&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;A nice feature of this action is that if issues get detected like a site not being available a report gets created by the action.&lt;br&gt;
We use this report to create an automated issue based on this report via the &lt;a href="https://github.com/peter-evans/create-issue-from-file" rel="noopener noreferrer"&gt;Create Issue From File&lt;/a&gt; action. This way we do not need to check the results of the GitHub Action runs but see it immediately in our issues.&lt;/p&gt;

&lt;p&gt;Of course we do not want to have multiple issues for that, so if an issue exists and the action runs again it should update the existing issue. We achieve this, by combining &lt;a href="https://github.com/peter-evans/create-issue-from-file" rel="noopener noreferrer"&gt;Create Issue From File&lt;/a&gt; action with the &lt;a href="https://github.com/micalevisk/last-issue-action" rel="noopener noreferrer"&gt;Find Last Issue&lt;/a&gt;. To identify the issue, we use issue labels. Luckily the two action work perfectly together to cover this requirement.&lt;/p&gt;

&lt;p&gt;With these basic automations in place, let us look at the next topic namely issues.&lt;/p&gt;
&lt;h3&gt;
  
  
  Issues
&lt;/h3&gt;

&lt;p&gt;We use issues for bug reports as well as for feature requests. There are also freestyle issues that we might use internally, but the main interaction with the users is happening via bug reports and feature requests.&lt;/p&gt;

&lt;p&gt;In general, we offer two different issue templates for &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/ISSUE_TEMPLATE/bug_report.yml" rel="noopener noreferrer"&gt;bugs&lt;/a&gt; and &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/ISSUE_TEMPLATE/feature_request.yml" rel="noopener noreferrer"&gt;feature requests&lt;/a&gt; to make the data entry and filing the necessary information as easy as possible. &lt;/p&gt;

&lt;p&gt;For the optimal convenience for our users we use &lt;a href="https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms" rel="noopener noreferrer"&gt;issue forms&lt;/a&gt; as they make the data entry much easier by offering features like checkboxes, drop down menus etc. The template also comprises the labels that get attached to the issue once created.&lt;/p&gt;

&lt;p&gt;What to automate once an issue is filed. First, we do not want to manually add the issues to our central project board and so we trigger a &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/workflows/assign-issue-to-project.yml" rel="noopener noreferrer"&gt;workflow&lt;/a&gt; whenever an issue is opened that does this for us. &lt;/p&gt;

&lt;p&gt;Not too much work from our end as GitHub provides an action for this called &lt;a href="https://github.com/actions/add-to-project" rel="noopener noreferrer"&gt;actions/add-to-project&lt;/a&gt;. As GitHub projects are separate entities that are not directly linked to the repository a GitHub token is needed that contains the &lt;a href="https://github.com/actions/add-to-project?tab=readme-ov-file#creating-a-pat-and-adding-it-to-your-repository" rel="noopener noreferrer"&gt;right permissions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For feature requests we want to make people aware that we prioritize based on the feedback from the community. To make these rules of the game obvious, we add an automated &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/workflows/feature-request-response.yml" rel="noopener noreferrer"&gt;comment with a community note&lt;/a&gt; to every feature request using the &lt;a href="https://github.com/peter-evans/create-or-update-comment" rel="noopener noreferrer"&gt;Create or Update Comment&lt;/a&gt; action that adds a predefined text as a comment.&lt;/p&gt;

&lt;p&gt;There is one scenario that probably has crossed your path as a maintainer of a project: what if you need to clarify something that was brought up via an issue? &lt;/p&gt;

&lt;p&gt;Whenever we need a clarification from a reporter, we add a tag named &lt;code&gt;needs-author-feedback&lt;/code&gt; to the issue. This label gets removed whenever a new comment is added via a (you already guess it) &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/workflows/issue-comment-created.yml" rel="noopener noreferrer"&gt;workflow&lt;/a&gt; that uses the &lt;a href="https://github.com/actions-ecosystem/action-remove-labels" rel="noopener noreferrer"&gt;Action Remove Labels&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What if the reporter does not answer? At some point in time, you probably want to get rid of the issue. We handle this by another workflow using a action provided by GitHub called &lt;a href="https://dev.toactions/stale@v9"&gt;Close Stale Issues and PRs&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We use this in a &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/workflows/stale-handling.yml" rel="noopener noreferrer"&gt;periodic workflow&lt;/a&gt; to first add a stale message after 15 days of no reply and finally close the issue after additional 5 days. Every step as accompanied by a comment on the issue that gets created by the workflow. This keeps our repository clean without the need to continuously check for stale issues manually.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - What about questions? We use &lt;a href="https://docs.github.com/en/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt; to cover this area, but there is no automation needed for us so far in this context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That covers the handling of the issues. Let us look next at pull requests.&lt;/p&gt;
&lt;h3&gt;
  
  
  Pull requests
&lt;/h3&gt;

&lt;p&gt;As mentioned before, we require pull requests (PRs) for any code change in the repository. To have a structured code review process we have a &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/PULL_REQUEST_TEMPLATE.md" rel="noopener noreferrer"&gt;pull request template&lt;/a&gt; in place to keep the PRs uniform and help the provider as well as the reviewer with the main points that need to be described.&lt;/p&gt;

&lt;p&gt;There are some repetitive tasks that need to be done for each PR and as we are lazy, the following tasks are delegated to a &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/workflows/set-default-values-pr.yml" rel="noopener noreferrer"&gt;workflow&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding the PR to the central project board&lt;/li&gt;
&lt;li&gt;Adding the creator of the PR as assignee except for PRs opened by Dependabot&lt;/li&gt;
&lt;li&gt;Adding the next open milestone&lt;/li&gt;
&lt;li&gt;Setting the default labels based on the PR title&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We add the PR to the project board via the same &lt;a href="https://github.com/actions/add-to-project" rel="noopener noreferrer"&gt;action&lt;/a&gt; that we use for issues. The other three tasks are a bit more coding to do as we did not find any fitting Actions on the GitHub Marketplace. Fortunately, it is quite easy to fulfill the requirements with a bit of Bash or Node.js code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - When diving into the &lt;a href="https://docs.github.com/en/rest/about-the-rest-api/about-the-rest-api?apiVersion=2022-11-28" rel="noopener noreferrer"&gt;GitHub API&lt;/a&gt; to find the right endpoints you might be stunned that there is only a very limited number of dedicated endpoints for pull request. The reason for this is that technically pull requests are issues with some features on top. Check out the endpoints for issues and you will probably find what you are looking for.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To make the creator of the PR the assignee, we use the GitHub CLI that is available in the GitHub-hosted runners with this code snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt; &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Add creator as assignee&lt;/span&gt;
   &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.actor != 'dependabot[bot]' }}&lt;/span&gt;
   &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;GH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
   &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
     &lt;span class="s"&gt;gh api \&lt;/span&gt;
     &lt;span class="s"&gt;--method POST \&lt;/span&gt;
     &lt;span class="s"&gt;-H "Accept: application/vnd.github+json" \&lt;/span&gt;
     &lt;span class="s"&gt;-H "X-GitHub-Api-Version: 2022-11-28" \&lt;/span&gt;
     &lt;span class="s"&gt;/repos/${{github.repository}}/issues/${{github.event.number}}/assignees \&lt;/span&gt;
      &lt;span class="s"&gt;-f "assignees[]=${{github.actor}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the information available in the action about the actor (&lt;code&gt;github.actor&lt;/code&gt;) and call the API for the assignees. This is straightforward.&lt;/p&gt;

&lt;p&gt;What about the next open milestone and the labels?&lt;/p&gt;

&lt;p&gt;There is some more logic needed here, so we decided to use a small piece of Node.js code to achieve our goals. As a basis we use the &lt;a href="https://github.com/actions/github-script" rel="noopener noreferrer"&gt;GitHub Script&lt;/a&gt; action that contains a pre-authenticated &lt;a href="https://github.com/octokit/rest.js" rel="noopener noreferrer"&gt;Octokit REST client&lt;/a&gt; to call the API endpoints.&lt;/p&gt;

&lt;p&gt;For the milestone requirement we must get the next open milestone and assign it to the PR. This is done via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Add next milestone&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v7&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
     &lt;span class="s"&gt;const milestones = await github.rest.issues.listMilestones({&lt;/span&gt;
       &lt;span class="s"&gt;owner: context.repo.owner,&lt;/span&gt;
       &lt;span class="s"&gt;repo: context.repo.repo,&lt;/span&gt;
       &lt;span class="s"&gt;state: "open",&lt;/span&gt;
       &lt;span class="s"&gt;sort: "due_on",&lt;/span&gt;
       &lt;span class="s"&gt;direction: "asc"&lt;/span&gt;
     &lt;span class="s"&gt;})&lt;/span&gt;

     &lt;span class="s"&gt;await github.rest.issues.update({&lt;/span&gt;
        &lt;span class="s"&gt;owner: context.repo.owner,&lt;/span&gt;
        &lt;span class="s"&gt;repo: context.repo.repo,&lt;/span&gt;
        &lt;span class="s"&gt;issue_number: context.issue.number,&lt;/span&gt;
        &lt;span class="s"&gt;milestone: milestones.data[0].number&lt;/span&gt;
     &lt;span class="s"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GitHub Script action helps a lot as you can directly interact with the API without wrapping your head around authentication. You also get the context information of the workflow injected. For larger pieces of code, I prefer dedicated files that I then call via Node.js instead of putting the code in the action. For this, this might be a bit too much though.&lt;/p&gt;

&lt;p&gt;As a last piece we add the labels derived from the PR title that must follow the &lt;a href="https://www.conventionalcommits.org/" rel="noopener noreferrer"&gt;conventional commits&lt;/a&gt;. We use the keywords of the commit to distinguish which label to set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set default labels&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.actor != 'dependabot[bot]' }}&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v7&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;TITLE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.title }}&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;const title = process.env.TITLE;&lt;/span&gt;

      &lt;span class="s"&gt;let defaultLabels = [];&lt;/span&gt;
      &lt;span class="s"&gt;if (title.startsWith("feat:")) {&lt;/span&gt;
        &lt;span class="s"&gt;defaultLabels.push("enhancement");&lt;/span&gt;
      &lt;span class="s"&gt;} else if (title.startsWith("fix:")) {&lt;/span&gt;
        &lt;span class="s"&gt;defaultLabels.push("bug");&lt;/span&gt;
      &lt;span class="s"&gt;} else if (title.startsWith("docs:")) {&lt;/span&gt;
        &lt;span class="s"&gt;defaultLabels.push("documentation");&lt;/span&gt;
      &lt;span class="s"&gt;} else if (title.startsWith("test:")) {&lt;/span&gt;
        &lt;span class="s"&gt;defaultLabels.push("test setup", "internal", "ignore-for-release");&lt;/span&gt;
      &lt;span class="s"&gt;} else if (title.startsWith("refactor:")) {&lt;/span&gt;
        &lt;span class="s"&gt;defaultLabels.push("internal", "ignore-for-release", "refactoring");&lt;/span&gt;
      &lt;span class="s"&gt;} else {&lt;/span&gt;
        &lt;span class="s"&gt;defaultLabels.push("internal", "ignore-for-release");&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;

      &lt;span class="s"&gt;await github.rest.issues.addLabels({&lt;/span&gt;
        &lt;span class="s"&gt;owner: context.repo.owner,&lt;/span&gt;
        &lt;span class="s"&gt;repo: context.repo.repo,&lt;/span&gt;
        &lt;span class="s"&gt;issue_number: context.issue.number,&lt;/span&gt;
        &lt;span class="s"&gt;labels: defaultLabels&lt;/span&gt;
      &lt;span class="s"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We exclude PRs filed via Dependabot.&lt;/p&gt;

&lt;p&gt;Now the reviewer can focus on the review and does not need to click around to fulfill organizational requirements. Don't get me wrong: the labels and the assignment to the project are important, but this could and should be automated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - Maybe you ask yourself if I am aware of the GraphQL endpoints of GitHub. Yes I am, but up to now I had only one situation where I saw a benefit in using the GraphQL API without breaking my hands to get a result. You can achieve all what is mentioned here with GraphQLs, but I am happy with the REST endpoints ... maybe I am also not smart enough to use GraphQL 😉&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As for issues we might run into stale PRs. Their handling uses the same workflow as for stale issues described in the previous section.&lt;/p&gt;

&lt;p&gt;When opening a PR you also want to know if the code follows your quality standards, namely if tests pass etc. In the next section we describe what we automated when it comes to this area.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI
&lt;/h3&gt;

&lt;p&gt;When a PR is opened, the following workflows get triggered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We execute a &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/workflows/spellcheck.yml" rel="noopener noreferrer"&gt;spell check&lt;/a&gt; to see if any typos made their way into the PR.&lt;/li&gt;
&lt;li&gt;We validate the PR title to ensure that it follows the conventional commits logic. This is done via a &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/workflows/semantic-pr.yml" rel="noopener noreferrer"&gt;workflow&lt;/a&gt; that uses the &lt;a href="https://github.com/amannn/action-semantic-pull-request" rel="noopener noreferrer"&gt;action-semantic-pull-request&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition we have one main &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/workflows/test.yml" rel="noopener noreferrer"&gt;test workflow&lt;/a&gt; that bundle several checks, namely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can the Go project be built?&lt;/li&gt;
&lt;li&gt;Are the unit tests executed successfully?&lt;/li&gt;
&lt;li&gt;Is the documentation that is generated for the CLI is up to date?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All these parts are Go CLI commands executed in the runner of the workflow.&lt;/p&gt;

&lt;p&gt;As a last step we send the test coverage report from the unit tests to Sonar Cloud using the &lt;a href="https://github.com/SonarSource/sonarqube-scan-action" rel="noopener noreferrer"&gt;SonarQube Scan Action&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - There is one important thing to mention here when it comes to external tools and Dependabot. You usually use an API key or alike to be able to call external tools like Sonar Cloud. You store this information as a secret in your GitHub repository. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This works fine until a PR gets opened by Dependabot. For security purposes GitHub decided to not propagate the secret if the actor is Dependabot. To make the secret accessible to Dependabot, you must define them as &lt;a href="https://docs.github.com/en/code-security/dependabot/troubleshooting-dependabot/troubleshooting-dependabot-on-github-actions#restrictions-when-dependabot-triggers-events" rel="noopener noreferrer"&gt;Dependabot secrets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These checks running for a PR are also defined as required status checks that must pass as part of the main branch protection ruleset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration Test
&lt;/h3&gt;

&lt;p&gt;Besides the unit tests that are part of the test workflow we also implemented a &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/workflows/integration-test.yml" rel="noopener noreferrer"&gt;integration test workflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This workflow is more complex and interacts with real infrastructure. The idea is to execute the CLI functionality which imports infrastructure into Terraform and compare the created state with the expected state on resource level. &lt;/p&gt;

&lt;p&gt;The main flow is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install the CLI build from source&lt;/li&gt;
&lt;li&gt;Execute the CLI to execute the Terraform code generation&lt;/li&gt;
&lt;li&gt;Execute a state import&lt;/li&gt;
&lt;li&gt;Transfer the state into JSON format&lt;/li&gt;
&lt;li&gt;Compare the newly created state with a reference state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The comparison of the two JSON states is done via a Node.js script. The script is called as part of the GitHub Action and compares the two state files in JSON format.&lt;/p&gt;

&lt;p&gt;The workflow configuration to achieve this is mostly around bringing the state files in the right format. What is worth to mention is the storage of the reference state file.&lt;/p&gt;

&lt;p&gt;We store this as a secret in the GitHub repository. There is only one small obstacle: you cannot directly store a JSON file as a secret. &lt;/p&gt;

&lt;p&gt;Consequently, we use a little trick: we encode the JSON file as a base64 string and store the encoded string as a secret. When the workflow is executed we take the secret, decode it and pass it to the Node.js script.&lt;/p&gt;

&lt;p&gt;As we have all tests automated and in place, time for the fun part, releasing a new version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Release
&lt;/h3&gt;

&lt;p&gt;The moment of truth is for sure the creation of a new release of the CLI. We do so by pushing a new tag to the repository which triggers ...(drumroll) a &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/workflows/release.yml" rel="noopener noreferrer"&gt;workflow&lt;/a&gt;.&lt;br&gt;
This workflow uses the concept of &lt;a href="https://docs.github.com/en/actions/sharing-automations/reusing-workflows" rel="noopener noreferrer"&gt;reusable workflows&lt;/a&gt; to trigger the tests before the release via GoReleaser starts. Better safe than sorry!&lt;/p&gt;

&lt;p&gt;Be aware that you might need to explicitly propagate secrets to the called workflows (see &lt;a href="https://docs.github.com/en/actions/sharing-automations/reusing-workflows#using-inputs-and-secrets-in-a-reusable-workflow" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The workflow executes the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Call of the test workflow&lt;/li&gt;
&lt;li&gt;Call of the integration test workflow&lt;/li&gt;
&lt;li&gt;Execution of the GoReleaser if the test workflows have been executed successfully&lt;/li&gt;
&lt;li&gt;Generation of the documentation and deployment as GitHub page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the screenshot of a release run:&lt;/p&gt;

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

&lt;p&gt;The release notes are also created automatically via the GoReleaser step using GitHub native features. The format is defined in a &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/release.yml" rel="noopener noreferrer"&gt;dedicated configuration&lt;/a&gt; mapping the labels to the sections and getting a nice formatting with emojis&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - There is also a pre-release check workflow that contains the two test workflows. We trigger this one manually when a release date is approaching to avoid unwanted surprises on release day.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Generation of GH Pages
&lt;/h3&gt;

&lt;p&gt;The documentation of the CLI is provided via a GitHub page as part of the repository. We are using &lt;a href="https://www.mkdocs.org/" rel="noopener noreferrer"&gt;MkDocs&lt;/a&gt; to generate the content, but I think most of the tools in that area are well integrated with GitHub and GitHub Actions. &lt;/p&gt;

&lt;p&gt;The corresponding &lt;a href="https://github.com/SAP/terraform-exporter-btp/blob/main/.github/workflows/create-gh-page.yml" rel="noopener noreferrer"&gt;workflow&lt;/a&gt; is therefore short comprising the installation of the MkDocs CLI and then calling it with the &lt;code&gt;gh-deploy&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;This workflow can be triggered manually but is also integrated into the release process as final step. So, no manual hands-on needed for the documentation part.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about Automating GitHub Projects?
&lt;/h3&gt;

&lt;p&gt;GitHub Projects offer built-in &lt;a href="https://docs.github.com/en/issues/planning-and-tracking-with-projects/automating-your-project/using-the-built-in-automations" rel="noopener noreferrer"&gt;automation&lt;/a&gt; that we use to automatically set the status of an issue in the project namely on the events of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adding&lt;/li&gt;
&lt;li&gt;closing&lt;/li&gt;
&lt;li&gt;reopening&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;an issue.&lt;/p&gt;

&lt;p&gt;We did not yet run into the need to add GitHub Actions here; however, it would be possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Points to Consider
&lt;/h3&gt;

&lt;p&gt;There are two big point to consider when using GitHub Actions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To execute a workflow the configuration file must land in the &lt;em&gt;main&lt;/em&gt; branch. From then on you can run it from a branch. So, whenever you add a new GitHub Action, you are either a GitHub magician or you probably have to have at least two PRs end up with a running action&lt;/li&gt;
&lt;li&gt;GitHub does not offer a way to locally run a workflow. There are projects that try to fill this gap. One prominent example is &lt;a href="https://github.com/nektos/act" rel="noopener noreferrer"&gt;act&lt;/a&gt;.
However, I had it running on a Windows machine until it did not work anymore (probably a Docker update killed it) and I also had some issues on a Mac with Apple silicon. Again, probably due to Docker, but at the end it is cumbersome for the user, no matter what the root cause is.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are points that you must take into account when developing workflows. A bit of inconvenience, but from my experience worth it (and maybe you have more luck with act which would remove at least some pain).&lt;/p&gt;

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

&lt;p&gt;Maintaining one or maybe several open-source projects is work. Even if it is part of your job, you probably want to focus on improving the features and functions of the project and not deal with tedious repetitive tasks, that could be automated.&lt;/p&gt;

&lt;p&gt;In this blog post, we described the areas where we used GitHub Actions to automate these tasks by either using and combining existing Actions available on the GitHub Marketplace or using some custom bash or Node.js Code in combination with GitHub API to get stuff automated.&lt;/p&gt;

&lt;p&gt;Maybe you could get some inspiration on what is possible, maybe you know better ways to achieve this. If this is the case, I am curious to learn about that in the comments!&lt;/p&gt;

&lt;p&gt;What comes next for us? We are continuously evaluating if there are other points worth automating. Currently there is no hot take on our list, but let's see what the future will bring&lt;/p&gt;

&lt;p&gt;With that - happy automating with GitHub!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>github</category>
      <category>githubactions</category>
      <category>automation</category>
    </item>
    <item>
      <title>Experimenting with Terramate and SAP BTP</title>
      <dc:creator>Christian Lechner</dc:creator>
      <pubDate>Tue, 10 Sep 2024 07:34:09 +0000</pubDate>
      <link>https://forem.com/lechnerc77/experimenting-with-terramate-and-sap-btp-22m1</link>
      <guid>https://forem.com/lechnerc77/experimenting-with-terramate-and-sap-btp-22m1</guid>
      <description>&lt;h2&gt;
  
  
  UPDATE 13.12.2024
&lt;/h2&gt;

&lt;p&gt;I updated several parts of this blog post as I missed one feature of Terramate concerning the output sharing namely the option to &lt;code&gt;mock&lt;/code&gt; input. With this feature in place and the corresponding setting in the scripts, the challenge of setting up the stacks with dynamic dependencies worked.&lt;/p&gt;

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

&lt;p&gt;In case you are working with Terraform in a multi-provider setup you certainly also came across the following challenge:&lt;/p&gt;

&lt;p&gt;You want to create a resource 1 that depends on provider A and a resource 2 that depends on provider B. So far, so good, but the read-only data of resource 1 contains parts of the information that is needed to configure provider B. Resource 2 has a dependency to resource 1, so from a logical perspective things should work out, as from a runtime perspective provider B is needed once resource 1 and consequently all relevant information is available.&lt;/p&gt;

&lt;p&gt;Unfortunately, Terraform as well as OpenTofu require the provider configuration fully in place from the start. A dynamic configuration or a lazy loading of the provider configuration is not possible, and I did not come across any plans to provide such a functionality.&lt;/p&gt;

&lt;p&gt;In this blog post I want to present a possible solution leveraging &lt;em&gt;Terramate&lt;/em&gt;. As an example, I will be using one example from my "daily life" namely SAP BTP and the Cloud Foundry environment on SAP BTP. This setup perfectly matches the above challenge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Cloud Foundry environment (namely the organization) can be created via the Terraform provider for SAP BTP. &lt;em&gt;After&lt;/em&gt; the creation, we have access to the data relevant for authenticating against the Cloud Foundry provider namely the API endpoint of the environment.&lt;/li&gt;
&lt;li&gt;In addition, we need another information namely the ID of the Cloud Foundry organization to setup further resources in Cloud Foundry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, jackpot ... this is a challenge and it is for real. Let's see how we can resolve it or at least make life a bit easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status Quo
&lt;/h2&gt;

&lt;p&gt;Currently if we want to resolve the situation, we must execute Terraform in two steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setup the Cloud Foundry environment via the Terraform Provider for SAP BTP.&lt;/li&gt;
&lt;li&gt;Do the consequent setup in Cloud Foundry via a second Configuration, transferring the information from the first step to the second one e.g., via outputs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While this is doable in CI/CD pipelines e.g. via GitHub Actions, testing things locally gets painful. As a workaround you can write the output of step 1 to a &lt;code&gt;terraform.tfvars&lt;/code&gt; file of step 2, which gives some level of convenience, but with this setup the setup in the CI/CD scenario differs from your local setup which is also not desirable.&lt;/p&gt;

&lt;p&gt;This is where I think Terramate can help. Although it cannot do some black magic and make things work as we would love them to, we can get a more streamlined setup. What do we need for that? Let's first explore some (experimental) features of Terramate that can help us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terramate (Experimental) Features
&lt;/h2&gt;

&lt;p&gt;Terramate as a productivity tool &lt;em&gt;on top&lt;/em&gt; of Terraform (or OpenTofu) can support us with the orchestration of the Terraform flow. Our recipe we want to use contains the following ingredients:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://terramate.io/docs/cli/stacks/" rel="noopener noreferrer"&gt;Stacks&lt;/a&gt; which we will use to define self-contained units for the deployment&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://terramate.io/docs/cli/orchestration/order-of-execution#explicit-order-of-execution" rel="noopener noreferrer"&gt;Explicit Order of Execution&lt;/a&gt; which we use to ensure that in case of a full deployment the self-contained units are executed in the right sequence&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://terramate.io/docs/cli/orchestration/outputs-sharing" rel="noopener noreferrer"&gt;Outputs Sharing&lt;/a&gt;(experimental) for transferring the output of one stack to the dependent one&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://terramate.io/docs/cli/orchestration/scripts#running-scripts" rel="noopener noreferrer"&gt;Scripts&lt;/a&gt;(experimental) to split the Terraform specific commands from the Terramate commands and to avoid multiple commands to be keyed in reducing the chance to make a mistake.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see we will leverage some experimental features which "&lt;em&gt;might still be subject to changes in the future.&lt;/em&gt;". We love to live on the edge of new features, so we accept that, right?&lt;/p&gt;

&lt;p&gt;Let's see how we can bring these ingredients together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terramating the Experience of SAP BTP and Cloud Foundry
&lt;/h2&gt;

&lt;p&gt;Let us shortly recap the setup that we plan to have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On the SAP BTP side of the house we first want to setup a &lt;a href="https://registry.terraform.io/providers/SAP/btp/latest/docs/resources/subaccount" rel="noopener noreferrer"&gt;subaccount&lt;/a&gt; and an &lt;a href="https://registry.terraform.io/providers/SAP/btp/latest/docs/resources/subaccount_environment_instance" rel="noopener noreferrer"&gt;envrionment&lt;/a&gt; of type Cloud Foundry. This is done via the Terraform provider for &lt;a href="https://registry.terraform.io/providers/SAP/btp/latest" rel="noopener noreferrer"&gt;SAP BTP&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;After that we want to create a &lt;a href="https://registry.terraform.io/providers/SAP/cloudfoundry/latest/docs/resources/space" rel="noopener noreferrer"&gt;space&lt;/a&gt; in the Cloud Foundry environment and if applicable assign some &lt;a href="https://registry.terraform.io/providers/SAP/cloudfoundry/latest/docs/resources/space_role" rel="noopener noreferrer"&gt;space roles&lt;/a&gt;. This is done via the &lt;a href="https://registry.terraform.io/providers/SAP/cloudfoundry/latest" rel="noopener noreferrer"&gt;Terraform provider for ≈Cloud Foundry&lt;/a&gt; that needs the output of the previous setup for the configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code samples that you will see, will focus on the Terramate features mentioned above. I did not use the code generation feature of Terramate to avoid distracting from the main topics of this blog post. Having said that, we would probably add code generation to the setup when bringing things to production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the stack
&lt;/h3&gt;

&lt;p&gt;First we need to do is define the Terramate stacks. The kind of natural split is to have one stack for the subaccount and one for the Cloud Foundry setup. We achieve this via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"subacccount-dev"&lt;/span&gt; &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"Basics for BTP development setup"&lt;/span&gt; &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="s2"&gt;"subaccount,dev"&lt;/span&gt; stacks/subaccount_dev
terramate create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"cloudfoundry-dev"&lt;/span&gt; &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"CF for BTP dev setup"&lt;/span&gt; &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="s2"&gt;"cloudfoundry,dev"&lt;/span&gt; stacks/cloudfoundry_dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This results in this directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;| - stacks
|   | - subacount_dev
|   | - cloudfoundry_dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of the stacks contains the stack information in the &lt;code&gt;stack.tm.hcl&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Excellent. Let's move on to bring them in the right execution order.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the execution order
&lt;/h3&gt;

&lt;p&gt;Both stacks are on the same level, so no implicit dependency and consequently no order is defined. Nevertheless, we would like to have such an order. We could either use sub-stacks or we define the order explicitly. In this blog post we make use of the second option.&lt;/p&gt;

&lt;p&gt;We configure the order of execution via the &lt;code&gt;after&lt;/code&gt; block in the stack definition. As we want to instruct Terramate to execute the Cloud Foundry stack after the subaccount stack, we adjust the &lt;code&gt;stack.tm.hcl&lt;/code&gt; file accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cloudfoundry-dev"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CF for BTP dev setup"&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"cloudfoundry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"d5962b3f-3b79-412f-9970-93112741855a"&lt;/span&gt;
  &lt;span class="nx"&gt;after&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"tag:subaccount:dev"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can specify the &lt;code&gt;after&lt;/code&gt; block via tags as we did or via the path to the stack. Using the tags is considered as best practice and keeps us more flexible. We can validate the sequence via the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate list &lt;span class="nt"&gt;--run-order&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that we can add the configuration for the setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Terraform configuration
&lt;/h3&gt;

&lt;p&gt;I do not want to clutter this blog post with a lot of basic Terraform code, so the following snippets focus on the main bits and pieces of our storyline. You find the complete code on &lt;a href="https://github.com/btp-automation-scenarios/terramate-orchestrate" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;subaccount_dev&lt;/code&gt; stack we define the resources as well as the provider configuration as "usual". Although we need to define an output for the configuration to get access to the API as well as to the organization ID of the Cloud Foundry environment, we do not do that manually.&lt;/p&gt;

&lt;p&gt;Terramate will close the gap here. For a better understanding of the following code snippets, this is the &lt;code&gt;main.tf&lt;/code&gt; of the subaccount stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_uuid"&lt;/span&gt; &lt;span class="s2"&gt;"uuid"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;random_uuid&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;random_uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;
  &lt;span class="nx"&gt;subaccount_domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subaccount_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random_uuid&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;subaccount_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subaccount_name&lt;/span&gt;
  &lt;span class="nx"&gt;subaccount_cf_org&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subaccount_domain&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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="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="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"btp_subaccount"&lt;/span&gt; &lt;span class="s2"&gt;"sa_dev_base"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subaccount_name&lt;/span&gt;
  &lt;span class="nx"&gt;subdomain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;join&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="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sa-dev-base"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;random_uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Fetch all available environments for the subaccount&lt;/span&gt;
&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"btp_subaccount_environments"&lt;/span&gt; &lt;span class="s2"&gt;"all"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subaccount_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;btp_subaccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sa_dev_base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Take the landscape label from the first CF environment if no environment label is provided&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"terraform_data"&lt;/span&gt; &lt;span class="s2"&gt;"cf_landscape_label"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;btp_subaccount_environments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="nx"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_name&lt;/span&gt; &lt;span class="err"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"cloudfoundry"&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment_type&lt;/span&gt; &lt;span class="err"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"cloudfoundry"&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="nx"&gt;landscape_label&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Create the Cloud Foundry environment instance&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"btp_subaccount_environment_instance"&lt;/span&gt; &lt;span class="s2"&gt;"cfenv_dev_base"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subaccount_id&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;btp_subaccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sa_dev_base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subaccount_cf_org&lt;/span&gt;
  &lt;span class="nx"&gt;environment_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cloudfoundry"&lt;/span&gt;
  &lt;span class="nx"&gt;service_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cloudfoundry"&lt;/span&gt;
  &lt;span class="nx"&gt;plan_name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf_plan_name&lt;/span&gt;
  &lt;span class="nx"&gt;landscape_label&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;terraform_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf_landscape_label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;

  &lt;span class="nx"&gt;parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;instance_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subaccount_cf_org&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resource of interest is the &lt;code&gt;btp_subaccount_environment_instance&lt;/code&gt; which delivers the necessary information for the other stack.&lt;/p&gt;

&lt;p&gt;When it comes to the &lt;code&gt;cloudfoundry_dev&lt;/code&gt; stack, we also define the project with the usual structure, a &lt;code&gt;main.tf&lt;/code&gt; with the space creation and the space role assignment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"cloudfoundry_space"&lt;/span&gt; &lt;span class="s2"&gt;"dev_space"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf_space_name&lt;/span&gt;
  &lt;span class="nx"&gt;org&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf_org_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"cloudfoundry_space_role"&lt;/span&gt; &lt;span class="s2"&gt;"space_developer"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf_space_developers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"space_developer"&lt;/span&gt;
  &lt;span class="nx"&gt;space&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cloudfoundry_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"cloudfoundry_space_role"&lt;/span&gt; &lt;span class="s2"&gt;"space_manager"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf_space_managers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"space_manager"&lt;/span&gt;
  &lt;span class="nx"&gt;space&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cloudfoundry_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev_space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and a provider configuration that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cloudfoundry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sap/cloudfoundry"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.0.0-rc1"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"cloudfoundry"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;api_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf_api_url&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We see that the API URL in the provider configuration as well as the Cloud Foundry org ID is a variable (&lt;code&gt;var.cf_api_url&lt;/code&gt; and &lt;code&gt;var.cf_org_id&lt;/code&gt;). Defining the variables is a bit counterintuitive now. As for the outputs in the other stack we leave these two variables out of the &lt;code&gt;variables.tf&lt;/code&gt; file which looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"cf_space_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The name of the Cloud Foundry space."&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"cf_space_managers"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"List of managers for the Cloud Foundry space."&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"cf_space_developers"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"List of developers for the Cloud Foundry space."&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As for the outputs this looks weird at a first glance (and also at a second glance) but as the configuration is in place, we can define the necessary bits and pieces for the output sharing and close this gap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sharing the Output between stacks
&lt;/h3&gt;

&lt;p&gt;Now we sparkle some Terramate magic dust onto the stacks to connect the output of the &lt;code&gt;subaccount_dev&lt;/code&gt; stack with the input of the &lt;code&gt;cloudfoundry_dev&lt;/code&gt; stack.&lt;/p&gt;

&lt;p&gt;We define a backend (not to mix up with a remote backend of Terraform) for the sharing. To do so we create a file called &lt;code&gt;terramate.tm&lt;/code&gt; at the &lt;em&gt;root level&lt;/em&gt; (= one level above the stack directories) with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;terramate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;experiments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"outputs-sharing"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;sharing_backend&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;terraform&lt;/span&gt;
  &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sharing_generated.tf"&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"terraform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-json"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;config&lt;/code&gt; block advices Terramate to enable the experimental feature of output sharing. The &lt;code&gt;sharing_backend&lt;/code&gt; block defines which filename should be used to generate the content for the sharing namely the variables as well as the output via the &lt;code&gt;filename&lt;/code&gt; attribute. The &lt;code&gt;command&lt;/code&gt; attribute specifies the command Terramate should use to access the output. It's the usual suspect when it comes to outputs of Terraform.&lt;/p&gt;

&lt;p&gt;Next, we configure Terramate what is the &lt;em&gt;output&lt;/em&gt; that should be shared. We achieve this by adding a configuration inside of the &lt;code&gt;subaccount_dev&lt;/code&gt; stack that we call &lt;code&gt;sa_dev_config.tm&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "cf_api_url" {
  backend   = "default"
  value     = "${jsondecode(btp_subaccount_environment_instance.cfenv_dev_base.labels)["API Endpoint"]}"
  sensitive = false
}

output "cf_org_id" {
  backend   = "default"
  value     = btp_subaccount_environment_instance.cfenv_dev_base.platform_id
  sensitive = false
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a nutshell we define the output variables of the stack via &lt;code&gt;output&lt;/code&gt; blocks. The configuration tells Terramate which &lt;code&gt;backend&lt;/code&gt; to use (the &lt;code&gt;default&lt;/code&gt; one we created before), the &lt;code&gt;value&lt;/code&gt; of the output as well as the sensitivity of the value.&lt;/p&gt;

&lt;p&gt;As a counterpart we define the missing variables in the &lt;code&gt;cloudfoundry_dev&lt;/code&gt; stack via the file &lt;code&gt;cf_dev_config.tm&lt;/code&gt; inside of the stack directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="s2"&gt;"cf_api_url"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt;
  &lt;span class="nx"&gt;from_stack_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ca4662d3-b75d-4290-9842-5bb8ef924d97"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf_api_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="nx"&gt;mock&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://api.cf.ap21.hana.ondemand.com"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="s2"&gt;"cf_org_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt;
  &lt;span class="nx"&gt;from_stack_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ca4662d3-b75d-4290-9842-5bb8ef924d97"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf_org_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="nx"&gt;mock&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"917f57a1-8fee-43b3-b3a8-4bb4ce8259ab"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use &lt;code&gt;input&lt;/code&gt; blocks or the definition. Besides the &lt;code&gt;backend&lt;/code&gt; we specify the ID of the stack where the values come from (&lt;code&gt;from_stack_id&lt;/code&gt;) as well as the &lt;code&gt;value&lt;/code&gt;. Beware that we are using &lt;code&gt;terraform output -json&lt;/code&gt;, so we must reference the attribute of the JSON object via value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE 13.12.2024&lt;/strong&gt;: In addition, we know that the values won't be availabel during the planning phase. Hence, we add some mock data that should be used in case of an error to make the planning phase pass.&lt;/p&gt;

&lt;p&gt;With that we are good to go and can start the code generation of Terramate via:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;As a result, a new file will appear in the stacks named &lt;code&gt;sharing_generated.tf&lt;/code&gt;. This corresponds to our configuration and the content of the files closes the gap of the Terraform configuration of the step before by creating the missing output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TERRAMATE: GENERATED AUTOMATICALLY DO NOT EDIT&lt;/span&gt;

&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cf_api_url"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jsondecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;btp_subaccount_environment_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cfenv_dev_base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s2"&gt;"API Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cf_org_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;btp_subaccount_environment_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cfenv_dev_base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TERRAMATE: GENERATED AUTOMATICALLY DO NOT EDIT&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"cf_api_url"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"cf_org_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From a configuration perspective we are complete. Now we need to execute Terramate i.e. use Terramate to execute Terraform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Script it!
&lt;/h3&gt;

&lt;p&gt;Taking one step back, our goal is to automate the provisioning flow which means issue several Terraform commands. We do not want to do that manually firing one command at a time but we want to have a pre-defined flow for setting up and also tearing down the infrastructure.&lt;/p&gt;

&lt;p&gt;Again, Terramate offers a solution for that namely using &lt;em&gt;scripts&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;As the scripting is still in experimental stage, we must activate it in the already existing configuration &lt;code&gt;terramate.tm&lt;/code&gt;. We add the value &lt;code&gt;scripts&lt;/code&gt; to the &lt;code&gt;experiments&lt;/code&gt; list, so the file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;terramate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;experiments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"outputs-sharing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"scripts"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;sharing_backend&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;terraform&lt;/span&gt;
  &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sharing_generated.tf"&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"terraform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-json"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we define the two scripts. We call them &lt;code&gt;deploy.tm&lt;/code&gt; and &lt;code&gt;teardown.tm&lt;/code&gt; and store them in the &lt;code&gt;stacks&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;deploy.tm&lt;/code&gt; contains the command flow from initialization, validation, planning and applying of the Terraform configuration. According to the Terramate documentation the file has the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="s2"&gt;"deploy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform Deployment"&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Init, validate, plan, and apply Terraform changes."&lt;/span&gt;
    &lt;span class="nx"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"terraform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"init"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"terraform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"validate"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"terraform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"plan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-out"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"out.tfplan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-lock=false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;enable_sharing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="nx"&gt;mock_on_fail&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"terraform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"apply"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-input=false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-auto-approve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-lock-timeout=5m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"out.tfplan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;enable_sharing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="nx"&gt;mock_on_fail&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The structure is intuitive and consistent with other objects in Terramate. The only thing that must not be forgotten is to add the option that the &lt;em&gt;output sharing&lt;/em&gt; is enabled when executing the commands via &lt;code&gt;enable_sharing = true&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;UPDATE 13.12.2024&lt;/strong&gt; In addition we add the option &lt;code&gt;mock_on_fail = true&lt;/code&gt; which tells Terramate to fall back on the mock value if the real value from the dependent stack is not yet available.&lt;/p&gt;

&lt;p&gt;The script for the teardown follows the same logic and looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="s2"&gt;"teardown"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform Teardown"&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Destroy Terraform setup."&lt;/span&gt;
    &lt;span class="nx"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"terraform"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"destroy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-input=false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-auto-approve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-lock-timeout=5m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;enable_sharing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="nx"&gt;mock_on_fail&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let us first check if we did things correctly and Terramate finds the scripts. We do so via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate script list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should look like this:&lt;/p&gt;

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

&lt;p&gt;Looks good. Now let us check where the scripts can be applied based on their location in the directories. We do so via the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate script tree
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output looks like this:&lt;/p&gt;

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

&lt;p&gt;Also looks good. The scripts can be applied to our stacks.&lt;/p&gt;

&lt;p&gt;The directory structure with all the implementations in place finally looks like this:&lt;/p&gt;

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

&lt;p&gt;Now time for some action.&lt;/p&gt;

&lt;h3&gt;
  
  
  3, 2, 1 ... and Action
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;UPDATE 13.12.2024&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before we can start the fun part we add the required parameters in the stacks via &lt;code&gt;terraform.tfvar&lt;/code&gt; files, namely the &lt;code&gt;globalaccount&lt;/code&gt; in the SAP BTP-specific stack and the &lt;code&gt;cf_space_managers&lt;/code&gt; as well as the &lt;code&gt;cf_space_developers&lt;/code&gt; in the Cloud Foundry stack.&lt;/p&gt;

&lt;p&gt;Having the mocking in place we can finally overcome the challenge of the dependencies and can spin up everything with one single command.&lt;/p&gt;

&lt;p&gt;Let's bring all the bits and pieces together and spin up the infrastructure with the following commands (don't forget to set your authentication information for SAP BTP and Cloud Foundry before):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate script run &lt;span class="nt"&gt;-X&lt;/span&gt; deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that you will have all the desired resources up and running on SAP BTP. The combined output of Terramate and Terraform in the console tells exactly what happened and helps a lot if you are running into issues.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; I use the &lt;code&gt;-X&lt;/code&gt; option as I am lazy and do not want to commit every time, I do some changes or fixes (and I did some over the course of setting things up). In a productive setup I would not recommend that&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We can also tear things down via the second script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate script run &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="nt"&gt;--reverse&lt;/span&gt; teardown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mind the &lt;code&gt;--reverse&lt;/code&gt; as otherwise things might get funny (I never forget something like that ... I heard of people doing so but not me ;-) ).&lt;/p&gt;

&lt;p&gt;And that's it, we did it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to find the code
&lt;/h2&gt;

&lt;p&gt;If you want to take a closer look at the code, you find it on GitHub in this &lt;a href="https://github.com/btp-automation-scenarios/terramate-orchestrate" rel="noopener noreferrer"&gt;repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Overall, we must state there is no perfect way to solve the challenge we mentioned in the beginning. This is due to the way how Terraform works. One could work around this dependency issues for the provider configuration but it is always cumbersome and often leads to solutions that work differently when trying things out locally compared to CI/CD pipelines and are error-prone and hard to understand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE 13.12.2024&lt;/strong&gt;&lt;br&gt;
Terramate can help us with the setup to make it concise and at least from a user perspective more streamlined. The experimental feature of output sharing as well as scripts support resolve our challenge with a at least from my perspective clean and understandable solution following the paradigm of stacks that I think is a huge strength of Terramate.&lt;/p&gt;

&lt;p&gt;As we are using experimental features there are some points that could be improved from a documentation perspective. I could figure out everything after some time, but I think the documentation of the output sharing as well as the one for scripts might need some more love before making them GA.&lt;/p&gt;

&lt;p&gt;What also caused some confusion is that the VS Code plugin marked the files using the experimental syntax as having errors. The corresponding error message in the pop-up really helps you with figuring out if something is missing, but I would have expected that it also recognizes the overall configuration. Not a big thing though as you can validate the setup via the Terramate commands to see if everything is where and how it should be.&lt;/p&gt;

&lt;p&gt;As the features still evolve it would be great if the scripts would also support filtering for tags &lt;em&gt;inside&lt;/em&gt; of the script. That would streamline the setup even more.&lt;/p&gt;

&lt;p&gt;With that happy Terraforming and Terramating!&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>terramate</category>
      <category>sap</category>
      <category>btp</category>
    </item>
    <item>
      <title>Pulumi with Terraform – the easy way</title>
      <dc:creator>Christian Lechner</dc:creator>
      <pubDate>Fri, 30 Aug 2024 11:22:57 +0000</pubDate>
      <link>https://forem.com/lechnerc77/pulumi-with-terraform-the-easy-way-214a</link>
      <guid>https://forem.com/lechnerc77/pulumi-with-terraform-the-easy-way-214a</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Some time ago I did some experimentation with bridging the &lt;a href="https://registry.terraform.io/providers/SAP/btp/latest" rel="noopener noreferrer"&gt;Terraform Provider for SAP BTP&lt;/a&gt; to get a Pulumi provider. While this was not an easy task it was doable, and I could get things up and running.&lt;/p&gt;

&lt;p&gt;I learned that some improvements have been made for this exercise at the Kubernetes Community Days Munich 2024, but did not yet follow up on them. And that was good. Why? Because hot of the press a blog post landed in the Pulumi blogs that sounded too. Good to believe: &lt;a href="https://www.pulumi.com/blog/any-terraform-provider/" rel="noopener noreferrer"&gt;Introducing: Support For Using Any Terraform Provider with Pulumi&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What the blog post basically says is that with one pulumi command you can now create the Pulumi provider by referencing the Terraform provider for the language you want to write your setup with. Can you believe that? Well, I gave a try, and I was stunned. Curious what I saw? Then follow along.&lt;/p&gt;

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

&lt;p&gt;As I am working for SAP some disclaimer (that you are certainly used to from SAP folks): this blog post should show your options when you would like to use Pulumi for setting up resources on SAP BTP. There is no official support of Pulumi from SAP (at least at the time when writing this blog post).&lt;/p&gt;

&lt;p&gt;With this statement let the fun start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating Pulumi from the provider
&lt;/h2&gt;

&lt;p&gt;I had the Pulumi CLI installed and followed a long the blog post. As JavaScript and TypeScript are languages that are heavily used in the SAP ecosystem, I decided to go for Typescript for my Pulumi setup.&lt;/p&gt;

&lt;p&gt;After doing the authentication dance with &lt;code&gt;pulumi login&lt;/code&gt; I created a new TypeScipt project via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi new typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the scaffolding complete, the next step was the magic one: create a local Pulumi provider from the Terraform provider&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi package add terraform-provider SAP/btp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that there it was ... the SDK based on the Terraform provider:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tsi8kuakblsixel79su.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tsi8kuakblsixel79su.png" alt="File layout of generated Pulumi Typescript SDK for SAP BTP" width="315" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The generation just took a few seconds and looking into the code, it looked decent.&lt;/p&gt;

&lt;p&gt;What to do next? The output of the command also gives you advice on that namely executing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm add btp@file:sdks/btp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition, the output states to add the &lt;code&gt;import&lt;/code&gt; of the SDK to the &lt;code&gt;index.ts&lt;/code&gt;. Very convenient experience lowering the entry barrier a lot. Been there done that.&lt;/p&gt;

&lt;p&gt;But now the moment of truth has come. Let us give it a try and deploy some resources to SAP BTP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving it a try
&lt;/h2&gt;

&lt;p&gt;First, we need to put some code into the &lt;code&gt;index.ts&lt;/code&gt; so that Pulumi knows what to do. I did a simple setup with a directory and a subaccount underneath:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;btp&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;btp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;directory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;btp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-dir-from-pulumi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subacountParameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;btp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SubaccountArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my subaccount from pulumi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;parentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;us10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;subdomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-subaccount-from-pulumi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subaccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;btp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Subaccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-ssubaccount-from-pulumi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subacountParameters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing fancy, but for a first try that should do the trick. I did not want to do a production grade setup, so I added the necessary configuration parameters via CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;btp:globalaccount &lt;span class="s1"&gt;'MyGlobalAccountSubdomain'&lt;/span&gt;
pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;btp:username &lt;span class="s1"&gt;'My Email'&lt;/span&gt;
pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;btp:password &lt;span class="s1"&gt;'MySecretPassword'&lt;/span&gt; &lt;span class="nt"&gt;--secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuration done, so let't give the deploy a try:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The output of the CLI said that it worked:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsj4aefbq3x9kz70b32v7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsj4aefbq3x9kz70b32v7.png" alt="Output of pulumi up" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and so does the result on SAP BTP:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6aim12o4b3z7s7jkkqw3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6aim12o4b3z7s7jkkqw3.png" alt="Created resources on SAP BTP" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Awesome stuff!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The new functionality of Pulumi to create a language-specific SDK based on a Terraform provider is nothing less than breath-taking. It is straightforward to get the SDK and make use of it. The generated code (at least for TypeScript where I tested it) looks decent, so easy to generate and to use. Although the feature is brand-new when publishing this blog post, I can definitely recommend to give it a try.&lt;/p&gt;

&lt;p&gt;As to be expected for a new feature there are also some known gaps that probably will be closed over time like generating the documentation. This clearness on what's possible and what is not (yet) is highly appreciated.&lt;/p&gt;

&lt;p&gt;I also want to say that the experience with the Pulumi CLI is great. It has very good hints and makes your start especially for Pulumi newbie like me a smooth experience.&lt;/p&gt;

&lt;p&gt;With that happy Terrafo .... ahh Pulumiing!&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>pulumi</category>
      <category>infrastructureascode</category>
      <category>sap</category>
    </item>
    <item>
      <title>Mind the Terraform Modules</title>
      <dc:creator>Christian Lechner</dc:creator>
      <pubDate>Mon, 29 Jul 2024 06:26:25 +0000</pubDate>
      <link>https://forem.com/lechnerc77/-mind-the-terraform-modules-3p11</link>
      <guid>https://forem.com/lechnerc77/-mind-the-terraform-modules-3p11</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;One important feature of Terraform is the ability to structure your infrastructure configuration into reusable &lt;em&gt;modules&lt;/em&gt;. In this blog post I would like to give an overview about modules include digging into potential pitfalls and how to avoid them. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Although I am mentioning Terraform only in this blog post, the statements also apply to OpenTofu.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What are modules?
&lt;/h2&gt;

&lt;p&gt;Let us start with some (maybe confusing) terminology. Formally modules are defined as "[...] containers for multiple resources that are used together." (Source: &lt;a href="https://developer.hashicorp.com/terraform/language/modules" rel="noopener noreferrer"&gt;Terraform documentation&lt;/a&gt;). So, every collection &lt;code&gt;*.tf&lt;/code&gt; files kept together in a directory is considered a module. Not what would have to come to my mind first, but that is the definition.&lt;/p&gt;

&lt;p&gt;Based on that we can further distinguish between two types of modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;root&lt;/em&gt; module&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Child&lt;/em&gt; modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every Terraform configuration has a &lt;em&gt;root module&lt;/em&gt; that contains your Terraform configuration. That's the files that you typically write, when staring your configuration journey. From this root module you can call other modules the so called &lt;em&gt;child modules&lt;/em&gt;. In contrast to the root module, a child module can be used &lt;em&gt;multiple times&lt;/em&gt; in the same configuration. It can also be used &lt;em&gt;from several configurations&lt;/em&gt;. This is not possible for the root module. &lt;/p&gt;

&lt;p&gt;For the sake of keeping things simple I will use the term "module" as a reference to a child module from now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should I use modules?
&lt;/h2&gt;

&lt;p&gt;Thinking about the "as Code" aspect a module consequently represents a reusable block of configurations with an interface (the variables you define) and probably an output. This approach enables you to structure your Infrastructure as Code configurations in a well-structured. It also gives you the opportunity to adhere to the &lt;em&gt;Do Not Repeat Yourself&lt;/em&gt; (DRY) principle when crafting your configurations. &lt;/p&gt;

&lt;p&gt;Modules in Terraform represent the same approach that you would take when working with "classical" application code: you would encapsulate functionality to give your code a clear structure. If there are bits and pieces that could be used in several spots you would probably put them in reusable assets.&lt;/p&gt;

&lt;p&gt;Besides the DRY aspect the modularization also helps you to keep your configuration code clean and readable and avoid human errors due to copy-pasting configurations all over the place (and then forgetting to apply fixes in all places). Not to forget, the usage of modules can support in ensuring compliance and governance with respect to the infrastructure configurations starting from ensuring naming conventions to the point of restricting e.g., the allowed VM sizes that you can provision via a module.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Of course modules do not exempt you to safeguarding things like allowed VM sizes also on platform level like via Azure policies on Azure as somebody might not use modules and directly provision resources or not use Terraform at all.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While the advantages of using modules (or modularize your code) are the same as with "traditional" application code, guess what the challenges are so too. I will come to this topic in a second touching the two angles of modules namely the provider and consumer part, but first let us have a look at how modules are defined in Terraform. &lt;/p&gt;

&lt;h2&gt;
  
  
  The anatomy of a module
&lt;/h2&gt;

&lt;p&gt;Following the definition of modules there is no specific keyword to define a module. However, when calling a module, you must use the keyword &lt;code&gt;module&lt;/code&gt; accompanied by the name of the module in your Terraform code. In addition, you must specify the source of the module via the &lt;code&gt;source&lt;/code&gt; attribute. There are several source types available like local files, Git repositories, a S3 bucket or the Terraform registry. All the supported sources are listed in the &lt;a href="https://developer.hashicorp.com/terraform/language/modules/sources" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Let us look at a simple example. We want to leverage a module to provision an AKS cluster on Azure. This would be quite a bit of work to stitch together all the resources manually. Luckily there is a module available for this in the &lt;a href="https://registry.terraform.io/modules/Azure/aks/azurerm/latest" rel="noopener noreferrer"&gt;Terraform registry&lt;/a&gt;. To call this module in our configuration the following code snippet would do the trick with a minimalistic configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"aks"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Azure/aks/azurerm"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"9.1.0"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg_my_aks_cluster"&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This module comprises 19 resources, so obviously leveraging the module makes the code more readable and maintainable as you do not have to care about how to set them up. There are also a lot more optional parameters that you can feed into the module (more than 150 to be precise) to further tailor your AKS cluster configuration. &lt;/p&gt;

&lt;p&gt;As you can see in the code snippet you can also specify a &lt;em&gt;version&lt;/em&gt; for the module. An important topic that we will discuss in the following section when it comes to consuming modules.&lt;/p&gt;

&lt;p&gt;Despite that the handling of modules is the same as with regular resources from a configuration perspective including meta-arguments like &lt;code&gt;depends_on&lt;/code&gt; or &lt;code&gt;count&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - If you are working with Azure, I want to draw your attention to the &lt;a href="https://azure.github.io/Azure-Verified-Modules/" rel="noopener noreferrer"&gt;Azure Verified Modules&lt;/a&gt;. The goal of this initiative is to provide a consolidate set of standards on how a good Infrastructure-as-Code module should look like. As it is Azure it does not only cover Terraform, but also Bicep modules. This project is not only of interest for consumption but also getting inspiration when you are building your own modules.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let us look at the two angles of modules the &lt;em&gt;provider&lt;/em&gt; and the &lt;em&gt;consumer&lt;/em&gt;, and what we need to keep in mind for each of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Modules - What to consider
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The content of a module
&lt;/h3&gt;

&lt;p&gt;When &lt;em&gt;building&lt;/em&gt; a module in Terraform the aspects that we must consider are the same as when implementing a library or any re-useable chunk of code. The first point to consider is the &lt;em&gt;design&lt;/em&gt; i.e. what resource combination should be contained in a module that makes the life of the user easier to provision a specific part on a cloud platform. The concrete number of resources that you combine in a module depends on your scenario. Nevertheless, avoid the following pitfalls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the module comprises only one resource you should rethink if this is worth a module.&lt;/li&gt;
&lt;li&gt;Crafting "god modules" that contain too many resources that are covering a too huge use case or setup. As in programming &lt;em&gt;high cohesion&lt;/em&gt; is the name of the game here. Bring together what logically belongs together and separate what does not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep in mind that the number of resources in a module also defines its blast radius when things go sideways. With a proper design namely a reasonable combination of resources that have a clear responsibility you can limit the blast radius at least to a reasonable extent. As an extreme example it makes sense to have a module for setting up an AKS cluster and its components, but it makes in my opinion no sense to encapsulate a complete stage setup like DEV, TEST or PROD in a single module.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage and versioning
&lt;/h3&gt;

&lt;p&gt;Once you have determined the boundaries of your module put the module into a dedicated code repository (one per module would be my advice) and make use of versioning of the module via the mechanics of the code repository. The consumer of the module will want to specify a version when calling the module. &lt;/p&gt;

&lt;p&gt;You can also push the modules to an official registry like the one of Hashicorp, but from a user perspective this is not a must. It depends on your use case and the requirements and constraints in your setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stick to common naming conventions
&lt;/h3&gt;

&lt;p&gt;When structuring your Terraform files for your module, stick to common conventions when it comes to structure and naming i.e., have something like a &lt;code&gt;main.tf&lt;/code&gt;, &lt;code&gt;variables.tf&lt;/code&gt; and &lt;code&gt;outputs.tf&lt;/code&gt; files (maybe with prefixes). This helps the consumer and the contributor to easily understand your module.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interface of the module
&lt;/h3&gt;

&lt;p&gt;You should define reasonable input and output variables. When it comes to the input variables also make sure to use default values wherever it makes sense to make the consumer's life easier. Also make sure that the variables are properly validated. &lt;a href="https://www.hashicorp.com/blog/terraform-1-9-enhances-input-variable-validations" rel="noopener noreferrer"&gt;Cross-object referencing&lt;/a&gt; that landed in Terraform 1.9 is something you should look at in that context. &lt;/p&gt;

&lt;p&gt;The validations and checks can be further improved via &lt;a href="https://developer.hashicorp.com/terraform/language/expressions/custom-conditions#preconditions-and-postconditions" rel="noopener noreferrer"&gt;pre- and post-condition blocks&lt;/a&gt;. The goal when designing a module must be to make it as easy and safe for the consumer to use it.&lt;/p&gt;

&lt;p&gt;Depending on your scenario you might also have the requirement to create resources conditionally. My advice is to make this explicit via a dedicated Boolean input variable instead of implicit derivations like "if variable X has a value and Variable Y and Z have no value then do create resource C otherwise not". Using a dedicated variable makes it also easier for the consumer to know what to expect and easier for you as provider of the module to validate the input.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write clean (Infrastructure as) Code
&lt;/h3&gt;

&lt;p&gt;Although the consumer does not see you code, it should be well structured and maintainable. This will help you to maintain the module in the long run and make contributions easier. In that context you might want to look at the &lt;a href="https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks" rel="noopener noreferrer"&gt;dynamic blocks&lt;/a&gt; that help you to get rid of repetitive code. &lt;/p&gt;

&lt;p&gt;However, take this with caution: making the code better readable and maintainable via dynamic blocks can turn into the opposite. So do not overuse them and especially do not nest them too deep. Of course, this always depends on the scenario, but worth to keep in mind.&lt;/p&gt;

&lt;p&gt;If you run into a situation where the nesting gets too deep, it might also be worth to look at tools that allow to generate Terraform code like &lt;a href="https://terramate.io/" rel="noopener noreferrer"&gt;Terramate&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;p&gt;Having good documentation including examples and sample use cases are a must. No difference to any other software artifact. Make it easy for your user to understand what the module does and how to use it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;"With great power comes great responsibility" - as with every reusable code block this is also true for modules. You have only limited control about who uses your module where and how. Consequently, testing your modules is an important part of the process. You should use all options that Terraform provides (see e.g., this blog post about &lt;a href="https://www.hashicorp.com/blog/testing-hashicorp-terraform" rel="noopener noreferrer"&gt;Testing HashiCorp Terraform&lt;/a&gt;) and automate the process via CI/CD pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  One word about local modules
&lt;/h3&gt;

&lt;p&gt;You can of course also use local modules to improve the structure of your code. I would see this as a first step  from a monolithic configuration to a modularized monolith. Nevertheless, the lifecycle for your configuration is still tightly coupled. &lt;/p&gt;

&lt;p&gt;With local modules there is also no option to reuse this code in other configurations. This can be a valid step to evaluate and get an impression what might make sense to be extracted into a module but is maybe not yet the final step if you want to foster reuse.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Using Modules - What to consider
&lt;/h2&gt;

&lt;p&gt;Let us switch perspective and put ourselves in the shoes of a consumer. Assume that we have found a module that perfectly fits our needs and follows all the best practices described above. &lt;/p&gt;

&lt;p&gt;There is one important thing as a caller we must consider: specify the &lt;em&gt;version&lt;/em&gt; of the module. This way we make a basic step towards safeguarding the setup by (hopefully) always using the same version and doing version upgrades intentionally. As mentioned before for modules from the Terraform registry this is achieved via the &lt;code&gt;version&lt;/code&gt; attribute in the &lt;code&gt;module&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"aks"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Azure/aks/azurerm"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"9.1.0"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg_my_aks_cluster"&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using a git repository you can use the &lt;code&gt;ref&lt;/code&gt; argument and target a released version or (my preference) the SHA hash. I highly recommend doing this to avoid unpleasant surprises. &lt;/p&gt;

&lt;p&gt;So, when specifying a version everything is great in terraform country, right? Right ...? Well, not perfectly. Let us dive a bit deeper into the mechanics.&lt;/p&gt;

&lt;p&gt;Let us assume the besides the module configuration above we also have specified a provider configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/azurerm"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3.113.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first step we do in our Terraform workflow is to run a &lt;code&gt;terraform init&lt;/code&gt;. This command downloads the modules and provider sources and stores them in the &lt;code&gt;.terraform&lt;/code&gt; directory. In the very first run it will select the newest available versions specified in your configuration that matches the given version constraint. With this information it will fill another file that landed in your file system, namely the &lt;code&gt;.terraform.lock.hcl&lt;/code&gt; file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2wmh30jizyet00pb06r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2wmh30jizyet00pb06r.png" alt="Terraform file system after terraform init" width="169" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The file is the so called &lt;a href="https://developer.hashicorp.com/terraform/language/files/dependency-lock" rel="noopener noreferrer"&gt;dependency lock file&lt;/a&gt;. It contains the checksums of the version that you downloaded. Once this lock file is there a consequent &lt;code&gt;terraform init&lt;/code&gt; will always fetch the version that was downloaded before even in case that a newer version exists that fulfills the version constraints. &lt;/p&gt;

&lt;p&gt;Taking a closer look at the lock file we see that it not only contains the version constraints, but &lt;br&gt;
also a checksum of the downloaded version:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6x4prsir4up6212tcd6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6x4prsir4up6212tcd6.png" alt="Terraform dependency lock file content" width="628" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Besides evaluating the existing version, Terraform will execute a &lt;a href="https://developer.hashicorp.com/terraform/language/files/dependency-lock#checksum-verification" rel="noopener noreferrer"&gt;checksum verification&lt;/a&gt; for the provider version. In case the checksum deviates for a version that exists in the lock file, Terraform will raise an error.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The initial verification of the checksum is up to you. This is not covered by any built-in mechanics of Terraform. There are also scenarios where Terraform might not be capable to verify the checksum. Please refer to the &lt;a href="https://developer.hashicorp.com/terraform/language/files/dependency-lock#checksum-verification" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for more details and the solution via &lt;a href="https://developer.hashicorp.com/terraform/cli/commands/providers/lock" rel="noopener noreferrer"&gt;terraform providers lock&lt;/a&gt; command.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's good and safeguards your setup. But wait ... what about the modules? They are also downloaded, but they are not reflected in the dependency lock file. If a module owner accidentally (or intentionally) updates a module but releases the update with the same version, this cannot be detected by Terraform at least not by the built-in functionality. &lt;/p&gt;

&lt;p&gt;That is not so cool when it comes to secure the supply chain of your infrastructure configuration. Is there a way to mitigate this? Fortunately Terraform has an ecosystem surrounding it and there is one quite new tool that can help you here: &lt;a href="https://github.com/ned1313/terrahash" rel="noopener noreferrer"&gt;terrahash&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This tool fills the gap mentioned before. You can create, store, and validate the checksums for the modules that you use with the &lt;code&gt;terrahash&lt;/code&gt; CLI. One word of caution before we take a closer look: &lt;code&gt;terrahash&lt;/code&gt; is in its early stages (version 0.1.0 when writing this post). Basic functionalities are there, but there are certainly missing features and maybe bugs. Nevertheless, it is worth to take a look. As it is open source, you can also contribute to it.&lt;/p&gt;

&lt;p&gt;With that in mind, let us see what this CLI can do for us.&lt;/p&gt;
&lt;h3&gt;
  
  
  Using terrahash
&lt;/h3&gt;

&lt;p&gt;The first thing that we do is execute the following command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This evaluates the current configuration and generates a &lt;code&gt;.terraform.module.lock.hcl&lt;/code&gt; file in analogy to the dependency lock file for the providers. The output of the command tells you exactly what it did:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0b2isu012p8osp0t7q1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0b2isu012p8osp0t7q1.png" alt="terrahash init output" width="530" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The content of the new file looks familiar:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8xir0375o2mkcr89lgh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8xir0375o2mkcr89lgh.png" alt="terrahash dependency lock file" width="677" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The file contains the checksums of the modules that you use. Looks good, now how to we validate the checksums? As this is an additional tool the checksum validation is not part of the Terraform CLI and must be triggered manually. However, in a productive setup with CI/CD pipeline this is not an issue as we can integrate this as additional pipeline step. We can trigger the check via the following command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The output in a success case looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30566dymjx6ltosr4xy8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30566dymjx6ltosr4xy8.png" alt="terrahash check output for success" width="432" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here we go in case of an error:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiv5csa0qznq43327fvob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiv5csa0qznq43327fvob.png" alt="terrahash check output for failure" width="548" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As promised this tool is a good addition to the Terraform setup when using modules and puts another piece in the puzzle of securing your infrastructure configurations with respect to unwanted version changes.&lt;/p&gt;

&lt;p&gt;For the sake of completeness as a consumer you should of course also take of care of testing your configurations, but in contrast to a reusable module the blast radius is limited to your configuration (which might already be worse enough if you are not doing it ... nothing gets your heart rate up like accidentally destroying resources on production)&lt;/p&gt;

&lt;p&gt;That's it from the perspective of a consumer. So let us summarize what we discussed in this blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In general. modules are a good approach to structure your IaC configurations. As I have my background in application development is interesting to see that the "as Code" aspects heavily come into play when thinking about modules. The exact same challenges arise as with "traditional" code be it from the perspective of a provider of module and the things you must warp your head around as soon as you provide reusable (Infrastructure as) code. Fortunately, you can use the same strategies and patterns to deal with these challenges.&lt;/p&gt;

&lt;p&gt;The same is true from a consumer perspective. Here the Terraform landscape has a gap when it comes to ensure the version integrity of the modules that you use. Luckily there is an ecosystem around Terraform and even more luckily with &lt;em&gt;terrahash&lt;/em&gt; a brand-new open-source tool is available that can help you to deal with this gap.&lt;/p&gt;

&lt;p&gt;Finally, I want to stress that although modules support you in your IaC journey towards a sustainable and maintainable setup, they are no silver bullet.  You need to look at several aspects of your setup and while modules can help you with reuse, there are challenges that you will not be able to solve with them and you should also take other tools into account like e.g., &lt;a href="https://terramate.io/" rel="noopener noreferrer"&gt;Terramate&lt;/a&gt;. One further advice if you are starting your journey: always take one step at a time, work iteratively, and do not try to boil the ocean by targeting the "perfect" setup from the beginning.&lt;/p&gt;

&lt;p&gt;With that … happy Terraforming and Terrahashing!&lt;/p&gt;

&lt;p&gt;P.S. I am also quite sure that I missed points and best practices to describe, so feel free to add them in the comments.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>infrastructureascode</category>
      <category>modules</category>
    </item>
    <item>
      <title>Terramate this SAP BTP!</title>
      <dc:creator>Christian Lechner</dc:creator>
      <pubDate>Mon, 17 Jun 2024 06:26:07 +0000</pubDate>
      <link>https://forem.com/lechnerc77/terramate-this-sap-btp-5a8p</link>
      <guid>https://forem.com/lechnerc77/terramate-this-sap-btp-5a8p</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Let us assume that we have a company that wants to standardize the setup of SAP BTP subaccounts. &lt;/p&gt;

&lt;p&gt;The company decides to define a standard setup of SAP BTP subaccounts with the following constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A subaccount should be created under a directory&lt;/li&gt;
&lt;li&gt;Every subaccount should get assigned some basic entitlements for app development&lt;/li&gt;
&lt;li&gt;A Cloud Foundry environment should be created &lt;/li&gt;
&lt;li&gt;Emergency subaccount admins should be added&lt;/li&gt;
&lt;li&gt;The resources should be labelled according to the company’s standards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, the company wants to leverage Terraform i.e. the &lt;a href="https://registry.terraform.io/providers/SAP/btp/latest" rel="noopener noreferrer"&gt;Terraform provider for SAP BTP&lt;/a&gt; for the setup. It uses a three-stage approach namely a subaccount for development, test, and production. &lt;/p&gt;

&lt;p&gt;To get some inspiration it checks the available samples for SAP BTP available on &lt;a href="https://github.com/SAP-samples/btp-terraform-samples" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. It finds a &lt;a href="https://github.com/SAP-samples/btp-terraform-samples/tree/main/released/usecases/dev_test_prod_setup" rel="noopener noreferrer"&gt;sample&lt;/a&gt; that seems to fit to the requirements. Maybe some adoptions are needed, but it is a good starting point.&lt;/p&gt;

&lt;p&gt;The sample uses a Terraform module to setup a subaccount. It creates the three stages by iterating over a list containing the stages and executes the module which results in the expected setup. &lt;/p&gt;

&lt;p&gt;While the example is a good starting point to get ideas on how to do such a setup and what options you have with Terraform, it also shows some drawbacks that we should address:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Although it makes sense to keep the setup logically together, the current configuration is tightly coupled in one configuration. The blast radius is high as all three stages might be affected by a change.&lt;/li&gt;
&lt;li&gt;It is difficult to distinguish environments when it comes to e.g., introducing variants depending on the stage. &lt;/li&gt;
&lt;li&gt;Let us say we want to test a new version of the module or a new version of the Terraform provider on the development environment, this is difficult to achieve.&lt;/li&gt;
&lt;li&gt;One huge state gets created containing all three environments and this is definitly a too tight coupling. This makes follow-up activities like drift detections hard to do.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let us be fair, the code on GitHub is just a sample, but it shows that you need to design the setup properly to avoid these drawbacks. &lt;/p&gt;

&lt;p&gt;The "usual" solution for this is to split the configuration into three, one for each stage. This makes things more self contained, but now we have another huge drawback: how do we keep the configurations in sync? Do we copy&amp;amp;paste the configurations? This is not a good idea as it is error-prone and not maintainable on the long run.&lt;/p&gt;

&lt;p&gt;Terraform per se does not provide a perfect solution for this, but fortunately there is a huge ecosystem around Terraform that we can leverage. One tool that is worth to take a closer look at is &lt;a href="https://terramate.io/" rel="noopener noreferrer"&gt;Terramate&lt;/a&gt;. This tool seems to exactly address the issues we are facing. So let us try it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Terramate?
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Terramate&lt;/em&gt; according to its documentation is a &lt;em&gt;productivity tool&lt;/em&gt; for Terraform. Its value proposition is to fill the gaps mentioned before (and even more) that come into play when dealing especially with large/complex Terraform configurations by helping you around automation, orchestration, and observation.&lt;/p&gt;

&lt;p&gt;Now you might say: "oh no, not another tool" or at least "hopefully I do not need to learn/use another language in addition to the configuration language that comes with Terraform".&lt;/p&gt;

&lt;p&gt;I think this is where the Terramate team made some very smart decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terramate is acting &lt;em&gt;on top&lt;/em&gt; of Terraform, so no restrictions concerning standard Terraform functionality. It enhances the features and functionalities of Terraform by covering the gaps. It is "just" another CLI you must install, but it doesn’t try to be a substitute for the Terraform CLI.&lt;/li&gt;
&lt;li&gt;It uses the Terraform configuration language to configure the additional features and functions that come with Terramate. No need to leave the realms of the language you are anyway using when working with Terraform.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before diving into the application of Terramate we must do some homework around the concepts of Terramate.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - There is a lot of features and functionality that Terramate provides. We will not cover all of them in this blog post. We will focus on the features that help us to address the issues we are facing with the current setup. If you want to get an overview or want to dig deeper in some topics, you should check the &lt;a href="https://terramate.io/docs/" rel="noopener noreferrer"&gt;Terramate documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The main concept that will help us is Terramate's concept of &lt;em&gt;stacks&lt;/em&gt;. Stacks put an additional "layer" on top of Terraform by making the configuration and state a well-defined unit that can be managed via Terramate based on the Terramate configuration. Using stacks we can define stack-specific or stack agnostic Terramate functionality e.g.  code-generation that probably helps us getting rid of copy&amp;amp;pasting activities.&lt;/p&gt;

&lt;p&gt;Now you might say "great another layer equals more complexity ... as if the scenario wasn't complex enough". We will see soon that this is not the case. Of course, you must understand the additional concepts, but they fall in place quite well and help you to manage the complexity in a convenient way. Having said that, complex things remain complex (no matter what management tries to tell you), but Terramate helps you to manage the complexity in a more structured way.&lt;/p&gt;

&lt;p&gt;Before repeating what is anyway available in the Terramate &lt;a href="https://terramate.io/docs/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, let us make things tangible and rebuild the setup for the dev-test-prod scenario leveraging Terramate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying Terramate to the dev-test-prod scenario
&lt;/h2&gt;

&lt;p&gt;The staged setup with dev, test and production is a perfect candidate for applying the stack concept. Let us start from scratch in an empty directory. First, we create the following directory structure to host the stacks on the file system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;| - stacks
|   | - dev
|   | - &lt;span class="nb"&gt;test&lt;/span&gt;
|   | - prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we create the stack configuration for the three stages via the Terramate CLI in each of the directories via the Terramare CLI. We key in the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"development"&lt;/span&gt; &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"Stack for BTP development setup"&lt;/span&gt; &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt; stacks/dev
terramate create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"testing"&lt;/span&gt; &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"Stack for BTP testing setup"&lt;/span&gt; &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="s2"&gt;"tst"&lt;/span&gt; stacks/test
terramate create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt; &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"Stack for BTP production setup"&lt;/span&gt; &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="s2"&gt;"prd"&lt;/span&gt; stacks/prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we gave each stack a name, a description, and a tag. I love the concept of tags in general, so great to have this feature in Terramate to flexibly categorize the stacks.&lt;/p&gt;

&lt;p&gt;As a result, we find a &lt;code&gt;stack.tm.hcl&lt;/code&gt; file in every directory. This file contains the stack-specific metadata. Here an example for the development stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"development"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Stack for BTP development setup"&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"29300b60-f0bb-4dda-bb4a-f0320148b0ed"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see the content reflects the parameters we defined. In addition, it contains a unique identifier for the stack.&lt;/p&gt;

&lt;p&gt;After we have initialized the stacks, we want to put in the necessary Terraform configuration. We do not want to copy&amp;amp;paste the configuration back and forth, but we want to define things once and use the configuration in the different stacks considering the specific requirements for the stages.&lt;/p&gt;

&lt;p&gt;To solve that Terramate brings another functionality to the table namely &lt;a href="https://terramate.io/docs/cli/code-generation/" rel="noopener noreferrer"&gt;code genration&lt;/a&gt;. We will use this feature to generate the basic setup for the stacks. I am a big pro-ponent of code generation instead of generic magic, so this feature is very appealing to me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating the Terraform configuration
&lt;/h3&gt;

&lt;p&gt;Let us now create the stage/stack-specific Terraform configurations. We start with the provider configuration. The layout of the provider should be the same for all three stages. However, we want to allow some flexibility when it comes to the development stage to test new versions of the Terraform CLI as well as new versions of the Terraform provider.&lt;/p&gt;

&lt;p&gt;To achieve that we define &lt;a href="https://terramate.io/docs/cli/reference/variables/globals" rel="noopener noreferrer"&gt;global variables&lt;/a&gt; that can be accessed in the code generation process. We put these variables in the &lt;code&gt;configs.tm.hcl&lt;/code&gt; file in the root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Configure default Terraform version and default providers&lt;/span&gt;
&lt;span class="nx"&gt;globals&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.6.0"&lt;/span&gt;
  &lt;span class="nx"&gt;version_dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.8.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;globals&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&lt;/span&gt; &lt;span class="s2"&gt;"providers"&lt;/span&gt; &lt;span class="s2"&gt;"btp"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.4.0"&lt;/span&gt;
  &lt;span class="nx"&gt;version_dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.4.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;globals&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&lt;/span&gt; &lt;span class="s2"&gt;"modules"&lt;/span&gt; &lt;span class="s2"&gt;"btp_subaccount_module"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/btp-automation-scenarios/btp-subaccount-module?ref=67cb61948e19497377fb4e23f01dd301319c6907"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we specify several version constraints as well as the source of the module on GitHub.&lt;br&gt;
Next, we create a directory called &lt;code&gt;templates&lt;/code&gt; where we will put in the code templates for the code generation. We add a file called &lt;code&gt;generate_provider.tm.hcl&lt;/code&gt; which serves as template for the code generation resulting in a provider configuration. It contains the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;generate_hcl&lt;/span&gt; &lt;span class="s2"&gt;"_terramate_generated_provider.tf"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tm_ternary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tm_contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;terramate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;terraform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version_dev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;terraform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;btp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SAP/btp"&lt;/span&gt;
          &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tm_ternary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tm_contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;terramate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;terraform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;btp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version_dev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;terraform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;btp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"btp"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;globalaccount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;globalaccount&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The syntax is good to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://terramate.io/docs/cli/code-generation/generate-hcl" rel="noopener noreferrer"&gt;&lt;code&gt;generate_hcl&lt;/code&gt; block&lt;/a&gt; defines the contents of the configuration to be generated.&lt;/li&gt;
&lt;li&gt;The block label &lt;code&gt;"_terramate_generated_provider.tf"&lt;/code&gt; defines the file name of the generated file.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;content&lt;/code&gt; block contains the actual content of the file. Here we define the provider configuration. As you can see, we make use of some Terramate specific functions to assign the version of the Terraform CLI and the Terraform provider depending on the stage with the help of the global variables and the stack specific information. We check if the stack is tagged as “dev” using the &lt;a href="//"&gt;&lt;code&gt;tm_contains&lt;/code&gt;&lt;/a&gt; function together with the information available via the &lt;code&gt;terramate.stack&lt;/code&gt; object. Depending on the availability we decide which version to use leveraging the &lt;a href="https://terramate.io/docs/cli/reference/functions/tm_ternary" rel="noopener noreferrer"&gt;tm_ternary&lt;/a&gt; function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One last piece is missing that we need for the generation: Teramate needs to know which templates to use for the generation. We define this in the &lt;code&gt;imports.tm.hcl&lt;/code&gt; file on root level. The file has the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Import helper files&lt;/span&gt;
&lt;span class="nx"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./templates/generate_provider.tm.hcl"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, ready to go? Let us kick off our first code generation via:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;We see the following output:&lt;/p&gt;

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

&lt;p&gt;And indeed, every stack now contains a generated provider.tf file according to our naming:&lt;/p&gt;

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

&lt;p&gt;Next stop: the variables. We want to make the call of the setup as easy as possible, so we will default the values where possible i.e., we will only leave the &lt;code&gt;globalaccount&lt;/code&gt; variable open for the user to provide. We will also make use of the stack specific information to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assign the architect role to the emergency admins in the development stage, otherwise the organizationally assigned admins&lt;/li&gt;
&lt;li&gt;Assign the entitlement &lt;code&gt;free&lt;/code&gt; for &lt;code&gt;HANA Cloud&lt;/code&gt; in the development stage, otherwise the entitlement &lt;code&gt;hana&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve this, we create a new template file for the variables in the &lt;code&gt;templates&lt;/code&gt; directory called &lt;code&gt;generate_variables.tm.hcl&lt;/code&gt;. We put the following code into the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;generate_hcl&lt;/span&gt; &lt;span class="s2"&gt;"_terramate_generated_variables.tf"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"globalaccount"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
      &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The globalaccount subdomain where the sub account shall be created."&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
      &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The region where the account shall be created in."&lt;/span&gt;
      &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us10"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"unit"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
      &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Defines to which organisation the sub account shall belong to."&lt;/span&gt;
      &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Sales"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"unit_shortname"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
      &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Short name for the organisation the sub account shall belong to."&lt;/span&gt;
      &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sls"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"architect"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
      &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Defines the email address of the architect for the subaccount"&lt;/span&gt;
      &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"genius.architect@test.com"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"costcenter"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
      &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Defines the costcenter for the subaccount"&lt;/span&gt;
      &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1234509874"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"owner"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
      &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Defines the owner of the subaccount"&lt;/span&gt;
      &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"someowner@test.com"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"team"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
      &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Defines the team of the sub account"&lt;/span&gt;
      &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awesome_dev_team@test.com"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"emergency_admins"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Defines the colleagues who are added to each subaccount as emergency administrators."&lt;/span&gt;
      &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tm_ternary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tm_contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;terramate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"somearchitect@test.com"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"jane.doe@test.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"john.doe@test.com"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"entitlements"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"List of entitlements for a BTP subaccount"&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;group&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
        &lt;span class="nx"&gt;type&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
        &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
        &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
        &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;
      &lt;span class="p"&gt;}))&lt;/span&gt;

      &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;group&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Audit + Application Log"&lt;/span&gt;
          &lt;span class="nx"&gt;type&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"service"&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"auditlog-viewer"&lt;/span&gt;
          &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"free"&lt;/span&gt;
          &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;group&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Alert"&lt;/span&gt;
          &lt;span class="nx"&gt;type&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"service"&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"alert-notification"&lt;/span&gt;
          &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"standard"&lt;/span&gt;
          &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;group&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SAP HANA Cloud"&lt;/span&gt;
          &lt;span class="nx"&gt;type&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"service"&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hana-cloud"&lt;/span&gt;
          &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tm_ternary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tm_contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;terramate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"hana"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;group&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SAP HANA Cloud"&lt;/span&gt;
          &lt;span class="nx"&gt;type&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"service"&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hana"&lt;/span&gt;
          &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hdi-shared"&lt;/span&gt;
          &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add the template to the &lt;code&gt;imports.tm.hcl&lt;/code&gt; file that now contains two blocks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Import helper files&lt;/span&gt;
&lt;span class="nx"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./templates/generate_provider.tm.hcl"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./templates/generate_variables.tm.hcl"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good to go for a second round of code generation:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The output shows that the variables are generated for every stack:&lt;/p&gt;

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

&lt;p&gt;And we also find the generated files in the stacks:&lt;/p&gt;

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

&lt;p&gt;But hold on a second: the output also shows that only the delta is generated. Files that do not need to be touched are not changed even if we re-run the &lt;code&gt;terramate generate&lt;/code&gt; command. I like that!&lt;/p&gt;

&lt;p&gt;Last but not least we must generate the main configuration file. The only requirement we have is that the &lt;code&gt;usage&lt;/code&gt; attribute of the subaccount is set to &lt;code&gt;USED_FOR_PRODUCTION&lt;/code&gt; in the testing and production stage, while the development environment should be set to &lt;code&gt;NOT_USED_FOR_PRODUCTION&lt;/code&gt;. In addition, we want to use the label of the stack to feed into the &lt;code&gt;stage&lt;/code&gt; parameter of the module that creates the subaccount.&lt;/p&gt;

&lt;p&gt;We know the drill by now and create a new template called &lt;code&gt;generate_main.tm.hcl&lt;/code&gt; in the &lt;code&gt;templates&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;generate_hcl&lt;/span&gt; &lt;span class="s2"&gt;"_terramate_generated_main.tf"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nx"&gt;lets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;stage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tm_upper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tm_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;terramate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&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="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# ------------------------------------------------------------------------------------------------------&lt;/span&gt;
    &lt;span class="c1"&gt;# Creation of directory&lt;/span&gt;
    &lt;span class="c1"&gt;# ------------------------------------------------------------------------------------------------------&lt;/span&gt;
    &lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"btp_directory"&lt;/span&gt; &lt;span class="s2"&gt;"parent"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;terramate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"This is the parent directory for &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;terramate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
      &lt;span class="nx"&gt;labels&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"architect"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;architect&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"costcenter"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;costcenter&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"owner"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"team"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# ------------------------------------------------------------------------------------------------------&lt;/span&gt;
    &lt;span class="c1"&gt;# Call module for creating subaccoun&lt;/span&gt;
    &lt;span class="c1"&gt;# ------------------------------------------------------------------------------------------------------&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"project_setup"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;terraform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;btp_subaccount_module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

      &lt;span class="nx"&gt;stage&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;let&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;

      &lt;span class="nx"&gt;unit&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit&lt;/span&gt;
      &lt;span class="nx"&gt;unit_shortname&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit_shortname&lt;/span&gt;
      &lt;span class="nx"&gt;architect&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;architect&lt;/span&gt;
      &lt;span class="nx"&gt;costcenter&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;costcenter&lt;/span&gt;
      &lt;span class="nx"&gt;owner&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;
      &lt;span class="nx"&gt;team&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;
      &lt;span class="nx"&gt;emergency_admins&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emergency_admins&lt;/span&gt;
      &lt;span class="nx"&gt;parent_directory_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;btp_directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
      &lt;span class="nx"&gt;usage&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tm_ternary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tm_contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;terramate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"NOT_USED_FOR_PRODUCTION"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"USED_FOR_PRODUCTION"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The source of the module is defined via the global variable we defined before, and the usage is set depending on the stack label using the known Terramate functions. For the sake of demoing the capabilities we introduced local variables via a &lt;a href="https://terramate.io/docs/cli/reference/variables/lets" rel="noopener noreferrer"&gt;&lt;code&gt;lets&lt;/code&gt; block&lt;/a&gt; to define the &lt;code&gt;stage&lt;/code&gt; variable. We make use of two further Terramate functions to get the first element of the stack tags (&lt;a href="https://terramate.io/docs/cli/reference/functions/tm_element" rel="noopener noreferrer"&gt;&lt;code&gt;tm_element&lt;/code&gt;&lt;/a&gt;) and to convert it to upper case (&lt;a href="https://terramate.io/docs/cli/reference/functions/tm_upper" rel="noopener noreferrer"&gt;&lt;code&gt;tm_upper&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;We add the import to the &lt;code&gt;imports.tm.hcl&lt;/code&gt; file which now finally looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Import helper files&lt;/span&gt;
&lt;span class="nx"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./templates/generate_provider.tm.hcl"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./templates/generate_variables.tm.hcl"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./templates/generate_main.tm.hcl"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the execution of the generation command, we see the &lt;code&gt;main.tf&lt;/code&gt; files with the expected content in the stacks:&lt;/p&gt;

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

&lt;p&gt;Shortly summarizing what we did until now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We created Terramate stacks with some metadata representing the stages of the setup&lt;/li&gt;
&lt;li&gt;We used the Terramate code generation feature to generate the provider configuration, the variables, and the main configuration for the Terraform setup including stack specific adjustments. As a consequence, we did not need to repeat ourselves or do some copy&amp;amp;paste exercises. In addition, we can now easily adapt the configuration in the future and regenerate the files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now time to get some infrastructure set up!&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Terramate to run Terraform commands
&lt;/h3&gt;

&lt;p&gt;As we have a complete Terraform configuration in the single directories, we could hop into each of those and execute the Terraform commands there. But can't we do better with Terramate? Yes, we can.&lt;/p&gt;

&lt;p&gt;Terramate has the &lt;a href="https://terramate.io/docs/cli/reference/cmdline/run" rel="noopener noreferrer"&gt;&lt;code&gt;terramate run&lt;/code&gt; command&lt;/a&gt; that allows you to execute &lt;em&gt;any&lt;/em&gt; command in the stacks. Let us try out some things.&lt;/p&gt;

&lt;p&gt;First, we want to initialize all stacks. we do so by executing the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate run terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the console we se that the Terraform commands gets executed per stack:&lt;/p&gt;

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

&lt;p&gt;We recognize that the usual suspects appear in the file system:&lt;/p&gt;

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

&lt;p&gt;Initialization successful. Next, we might want to plan the setup. We do so by executing the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate run terraform plan &lt;span class="nt"&gt;-var&lt;/span&gt; &lt;span class="nv"&gt;globalaccount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOU GLOBAL ACCOUNT&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the output we will see that the plan is executed for all three stacks in sequence. The output is a bit lengthy, so I will not provide a screenshot here. &lt;/p&gt;

&lt;p&gt;The output reflects that the plan is executed for all three stacks. We will also see a different number of resources that are to be created as we have only one emergency administrator for the development environment, while we defined two for the test and production environment. Things work as expected.&lt;/p&gt;

&lt;p&gt;We can also check the execution sequence via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate list &lt;span class="nt"&gt;--run-order&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output looks like this and as we have no dependencies all stacks are on the same level ordered by the sequence in the file system:&lt;/p&gt;

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

&lt;p&gt;The execution of the commands can also be done in parallel:&lt;/p&gt;

&lt;p&gt;Terramate will do the parallelization if the stacks are independent from each other. If there are dependencies between the stacks, Terramate will execute the stacks in the correct order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate run &lt;span class="nt"&gt;--parallel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3 terraform plan &lt;span class="nt"&gt;-var&lt;/span&gt; &lt;span class="nv"&gt;globalaccount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOU GLOBAL ACCOUNT&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is then reflected in the output of the command:&lt;/p&gt;

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

&lt;p&gt;We can also restrict the execution to a specific stack e.g., via the path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate run &lt;span class="nt"&gt;--chdir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;stacks/dev terraform plan &lt;span class="nt"&gt;-var&lt;/span&gt; &lt;span class="nv"&gt;globalaccount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOU GLOBAL ACCOUNT&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or even more convenient using tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terramate run &lt;span class="nt"&gt;--tags&lt;/span&gt; dev terraform plan &lt;span class="nt"&gt;-var&lt;/span&gt; &lt;span class="nv"&gt;globalaccount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOU GLOBAL ACCOUNT&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output reflects the filtering:&lt;/p&gt;

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

&lt;p&gt;You can of course also execute all the other commands and apply your setup. But I think the value of executing the commands via Terramate is clear now.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - Maybe you stumble across Terramate's built-in safeguarding to prevent executions on uncommitted changes when doing things on your machine. You can switch off all safeguards by adding the &lt;code&gt;-X&lt;/code&gt; flag to the &lt;code&gt;terramate run&lt;/code&gt; commands. It certainly makes sense though to check the &lt;a href="https://terramate.io/docs/cli/reference/cmdline/run#options" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; what are the effects before doing so.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Where to find the code
&lt;/h2&gt;

&lt;p&gt;You find the code including a copy of the original monolithic setup on GitHub: &lt;a href="https://github.com/btp-automation-scenarios/btp-terramate" rel="noopener noreferrer"&gt;https://github.com/btp-automation-scenarios/btp-terramate&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion and Outlook
&lt;/h2&gt;

&lt;p&gt;When dealing with more complex Terraform setups that usually arise quickly, Terramate is a very valuable tool that you should take a look at. It helps you to manage the complexity in a structured way and comes with useful features to make your life in the Terraform world easier.&lt;/p&gt;

&lt;p&gt;The rewriting of the dev-test-prod setup showed where Terramate could shine and helps us tackling the complexity that comes with these setups. Two main ingredients for achieving this is the concept of stacks and the code generation feature.&lt;/p&gt;

&lt;p&gt;However, we left out one important aspect of remote backends. Here again the stack-based approach comes in handy as you can see in this sample provided by the Terramate team that showcases how to generate stack specific backend configurations: &lt;a href="https://github.com/terramate-io/terramate-examples/blob/main/01-keep-terraform-dry/imports/generate_backend.tm.hcl" rel="noopener noreferrer"&gt;https://github.com/terramate-io/terramate-examples/blob/main/01-keep-terraform-dry/imports/generate_backend.tm.hcl&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is so much more to explore in Terramate and in which scenarios it can support your setup. Looking at the documentation I just scratched the surface and need to spend a bit more time to understand all the details. But I am convinced it is worth it.&lt;/p&gt;

&lt;p&gt;With that happy Terraforming with Terramate!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - I focused on Terraform in this blog post, but you can of course also use &lt;a href="https://opentofu.org/" rel="noopener noreferrer"&gt;OpenTofu&lt;/a&gt;. There is no barrier built into Terramate that would prevent you from doing so.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sap</category>
      <category>btp</category>
      <category>terraform</category>
      <category>terramate</category>
    </item>
    <item>
      <title>Terraform - Let's keep the quality up!</title>
      <dc:creator>Christian Lechner</dc:creator>
      <pubDate>Tue, 11 Jun 2024 06:07:10 +0000</pubDate>
      <link>https://forem.com/lechnerc77/terraform-lets-keep-the-quality-up-1abg</link>
      <guid>https://forem.com/lechnerc77/terraform-lets-keep-the-quality-up-1abg</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Terraform is one variant of managing Infrastructure as Code. Let us take a closer look at the the second part of the term namely at the "as-Code". What are good practices when it comes to the quality of the code when thinking about application development? Formatting and syntax checks as well as automated testing (unit and integration testing). &lt;/p&gt;

&lt;p&gt;Shouldn't we apply the same approaches that we do when developing code for applications also when developing Terraform configurations? You can already guess what the answer is ... yes of course we should. And the cool thing is: a lot of support is available out of the box when using Terraform.&lt;/p&gt;

&lt;p&gt;In this blog post we will walk through the different aspects that we should consider when writing Terraform configurations. We will cover the following topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code Formatting&lt;/li&gt;
&lt;li&gt;Code Validation&lt;/li&gt;
&lt;li&gt;Unit Testing&lt;/li&gt;
&lt;li&gt;Integration Testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the functionality will be demonstrated leveraging HashiCorp Terraform standard functionality using as an example the Terraform provider for SAP BTP. To automate the process, we will use GitHub Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Formatting
&lt;/h2&gt;

&lt;p&gt;Let us start with the very first step of ensuring the quality of the Terraform scripts - code formatting. The Terraform CLI provides a dedicated command &lt;code&gt;terraform fmt&lt;/code&gt; to format the Terraform configuration files.&lt;/p&gt;

&lt;p&gt;This command rewrites the Terraform configuration files to a canonical format and style. The good thing is that no configuration is needed as the formatting rules are predefined by Terraform itself. So no discussion in the teams how to format the code (yes I am looking at you JavaScript and the tons of options and source of infinite discussions).&lt;/p&gt;

&lt;p&gt;So as first step to ensure the quality of the Terraform code namely a proper code formatting, run the following command:&lt;/p&gt;

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

terraform &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nt"&gt;-recursive&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;I added the &lt;code&gt;-recursive&lt;/code&gt; flag to format all the files in the directory and its subdirectories. Good if you do that locally, but even better if you add it to your CI/CD pipeline assuming that changes in the Terraform scripts are done via pull requests (PR). &lt;/p&gt;

&lt;p&gt;As an example we use GitHub Actions to ensure that whenever a pull request is created the Terraform scripts are checked for proper formatting. If this is not the case we let the pipeline fail. For that we create a GitHub Action that is triggered by a PR and basically does two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check which directories contain changed files&lt;/li&gt;
&lt;li&gt;Run the &lt;code&gt;terraform fmt&lt;/code&gt; command on these directories to check if the formatting is correct&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The GitHub Action workflow looks like this:&lt;/p&gt;

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

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Format Check&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;opened&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;reopened&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;synchronize&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ready_for_review&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;terraform-fmt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate Format of Terraform Files&lt;/span&gt;
      &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
      &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
          &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Terraform&lt;/span&gt;
          &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v3&lt;/span&gt;
          &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;terraform_wrapper&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get changed directories&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;changed-files&lt;/span&gt;
          &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tj-actions/changed-files@v44&lt;/span&gt;
          &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;dir_names&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;

        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate Terraform format&lt;/span&gt;
          &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.changed-files.outputs.any_changed == 'true'&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;ALL_CHANGED_FILES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.changed-files.outputs.all_changed_files }}&lt;/span&gt;
          &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
          &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
              &lt;span class="s"&gt;EXIT_CODE=0&lt;/span&gt;
              &lt;span class="s"&gt;for file in ${ALL_CHANGED_FILES}; do&lt;/span&gt;
                &lt;span class="s"&gt;echo "Checking format of $file with terraform fmt"&lt;/span&gt;
                &lt;span class="s"&gt;terraform fmt -check -recursive "$file" || EXIT_CODE=$?&lt;/span&gt;
              &lt;span class="s"&gt;done&lt;/span&gt;
              &lt;span class="s"&gt;exit $EXIT_CODE&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We use the community action &lt;code&gt;tj-actions/changed-files&lt;/code&gt; to get the directories that contain changed files. The &lt;code&gt;terraform fmt&lt;/code&gt; command is then run on these directories. Here we used an additional flag &lt;code&gt;-check&lt;/code&gt; to check if the files are formatted correctly without formatting them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - There are a few more options available for the &lt;code&gt;terraform fmt&lt;/code&gt; that might come handy when executing the checks. You can find them in the &lt;a href="https://developer.hashicorp.com/terraform/cli/commands/fmt" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The GitHub Actions workflow can be integrated in the required checks for pull requests and this way only proper formatted Terraform scripts will land in your repository. What's next? Let's look at the code validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Validation
&lt;/h2&gt;

&lt;p&gt;Before executing a Terraform script it makes sense to statically check if the script is syntactically correct. This way we can already sort out some issues before the actual execution. &lt;/p&gt;

&lt;p&gt;As for the formatting the Terraform CLI got us covered via the command &lt;code&gt;terraform validate&lt;/code&gt;. In contrast to &lt;code&gt;terraform fmt&lt;/code&gt; this command has some prerequisites before being applicable namely you must have a initialized workspace. The backend configuration can be omitted. Before executing the validation, you must therefore execute the following command in the directory where your Terraform configuration is located:&lt;/p&gt;

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

terraform init &lt;span class="nt"&gt;-backend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;-backend=false&lt;/code&gt; is needed if you have a backend configuration in place, but also doesn't do any harm in case there is no backend configuration provided. After that you can execute the validation. Use the following command to trigger the validation:&lt;/p&gt;

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

terraform validate


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

&lt;/div&gt;

&lt;p&gt;This will check the syntax of the Terraform scripts in the directory it is executed in and will return errors if there are any. From a automation perspective it is great that the command can return the result of the validation in a machine-readable format namely JSON by using the &lt;code&gt;-json&lt;/code&gt; flag.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - You can find more information about the command and in particular the fields of the JSON object in the &lt;a href="https://developer.hashicorp.com/terraform/cli/commands/validate" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let us assume that we want to run this simple validation (without any further &lt;code&gt;jq&lt;/code&gt; magic on the JSON response) in our CI/CD pipeline, i.e. just execute the validation and in case it fails, let the pipeline fail. We can do so with the following GitHub Actions workflow:&lt;/p&gt;

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

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Validation Check&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;opened&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;reopened&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;synchronize&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ready_for_review&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;terraform-validate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate Syntax of Terraform Files&lt;/span&gt;
      &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
      &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
          &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Terraform&lt;/span&gt;
          &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v3&lt;/span&gt;
          &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;terraform_wrapper&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get changed directories&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;changed-files&lt;/span&gt;
          &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tj-actions/changed-files@v44&lt;/span&gt;
          &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;dir_names&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;

        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate Terraform sytnax&lt;/span&gt;
          &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.changed-files.outputs.any_changed == 'true'&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;ALL_CHANGED_FILES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.changed-files.outputs.all_changed_files }}&lt;/span&gt;
          &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
          &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
              &lt;span class="s"&gt;EXIT_CODE=0&lt;/span&gt;
              &lt;span class="s"&gt;for file in ${ALL_CHANGED_FILES}; do&lt;/span&gt;
                &lt;span class="s"&gt;echo "Validating Terraform files in $file with terraform validate"&lt;/span&gt;
                &lt;span class="s"&gt;cd $file&lt;/span&gt;
                &lt;span class="s"&gt;terraform init -backend=false || EXIT_CODE=$?&lt;/span&gt;
                &lt;span class="s"&gt;terraform validate || EXIT_CODE=$?&lt;/span&gt;
                &lt;span class="s"&gt;rm -rf .terraform/&lt;/span&gt;
                &lt;span class="s"&gt;cd ${{ github.workspace }}&lt;/span&gt;
              &lt;span class="s"&gt;done&lt;/span&gt;
              &lt;span class="s"&gt;exit $EXIT_CODE&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;As in the case of the formatting check we assume that the change is served via a PR. As before we check which directories are changed, iterate over the list of directories and execute the validation for each of the directories. In case any validation fails, the GitHub Action workflow will fail.&lt;/p&gt;

&lt;p&gt;Cool, so now we safeguarded the quality of the Terraform scripts by ensuring that they are properly formatted and that the syntax is correct. However, there is more that can be done using &lt;a href="https://www.hashicorp.com/blog/testing-hashicorp-terraform" rel="noopener noreferrer"&gt;Terraforms testing framework&lt;/a&gt;. This framework is available since release 1.6.0 and was since then improved e.g. with &lt;a href="https://www.hashicorp.com/blog/terraform-1-7-adds-test-mocking-and-config-driven-remove" rel="noopener noreferrer"&gt;test mocking&lt;/a&gt; in release 1.7.0. &lt;/p&gt;

&lt;p&gt;The good thing in contrast to other Terraform testing frameworks is, that you can write the test in the same syntax that you already know i.e. the HashiCorp configuration language. This lowers the entry barrier and makes the adoption easier. Lets us take a closer look how this framework can help us improve the code quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform Testing Framework
&lt;/h2&gt;

&lt;p&gt;If you take a look at the original blog post about the &lt;a href="https://www.hashicorp.com/blog/testing-hashicorp-terraform" rel="noopener noreferrer"&gt;testing framework&lt;/a&gt; by HashiCorp, you will recognize that it divides the tests into different categories that you certainly know from application development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit Tests&lt;/li&gt;
&lt;li&gt;Contract Tests&lt;/li&gt;
&lt;li&gt;Integration Tests&lt;/li&gt;
&lt;li&gt;End-to-End Tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this blog post we will focus on the categories of "Unit Tests" and "Integration Tests".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; If you are also using Terraform modules in your company, I highly recommend to take a look at the "Contract Tests" as described in the blog post &lt;a href="https://www.hashicorp.com/blog/testing-hashicorp-terraform" rel="noopener noreferrer"&gt;Testing HashiCorp Terraform&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Basic Setup
&lt;/h3&gt;

&lt;p&gt;Before we start with the testing let us first look at a basic setup. Assume we have a quite Terraform script that crates a subaccount on the SAP Business Technology Platform and assigns some entitlements to it. The configuration contains several input variables to ensure company guidelines for the naming and labeling of the subaccount. The &lt;code&gt;variables.tf&lt;/code&gt; has the following layout:&lt;/p&gt;

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

&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# Provider configuration&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"globalaccount"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The subdomain of the SAP BTP global account."&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The region where the project account shall be created in."&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu10"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# Subaccount setup&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"project_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The subaccount name."&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"proj-1234"&lt;/span&gt;

  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^[a-zA-Z0-9_&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;-]{1,200}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Provide a valid project name."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"stage"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The stage/tier the account will be used for."&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DEV"&lt;/span&gt;

  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"DEV"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"TST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"SBX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"PRD"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Select a valid stage for the project account."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"costcenter"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The cost center the account will be billed to."&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1234567890"&lt;/span&gt;

  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^[0-9]{10}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;costcenter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Provide a valid cost center."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"org_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Defines to which organisation the project account shall belong to."&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"B2C"&lt;/span&gt;

  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="c1"&gt;// Cross Development&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"B2B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"B2C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ECOMMERCE"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="c1"&gt;// Internal IT&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"PLATFORMDEV"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"INTIT"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;org_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Please select a valid org name for the project account."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# Entitlements for Subaccount&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"entitlements"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"List of entitlements for the subaccount."&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"alert-notification"&lt;/span&gt;
      &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"standard"&lt;/span&gt;
      &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SAPLaunchpad"&lt;/span&gt;
      &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"standard"&lt;/span&gt;
      &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hana-cloud"&lt;/span&gt;
      &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hana"&lt;/span&gt;
      &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hana"&lt;/span&gt;
      &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hdi-shared"&lt;/span&gt;
      &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sapappstudio"&lt;/span&gt;
      &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"standard-edition"&lt;/span&gt;
      &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The setup of the subaccount is given by the following &lt;code&gt;main.tf&lt;/code&gt; file:&lt;/p&gt;

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

&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# Setup of names in accordance to the company's naming conventions&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;project_subaccount_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;org_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: CF - &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;project_subaccount_domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;org_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;project_subaccount_cf_org&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;org_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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="s2"&gt;"_"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# Creation of subaccount&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"btp_subaccount"&lt;/span&gt; &lt;span class="s2"&gt;"project"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_subaccount_name&lt;/span&gt;
  &lt;span class="nx"&gt;subdomain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_subaccount_domain&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"stage"&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s2"&gt;"costcenter"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;costcenter&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;usage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"NOT_USED_FOR_PRODUCTION"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# Assignment of entitlements&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"btp_subaccount_entitlement"&lt;/span&gt; &lt;span class="s2"&gt;"entitlements"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entitlement&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entitlements&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;entitlement&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;subaccount_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;btp_subaccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;service_name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;plan_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This file translates the input variables into the right names and creates the resources. In addition we define output variables in the &lt;code&gt;outputs.tf&lt;/code&gt; file:&lt;/p&gt;

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

&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"subaccount_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;btp_subaccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The ID of the project subaccount."&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"subaccount_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;btp_subaccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The name of the project subaccount."&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;While this setup is quite simple, we see that some parts make sense to be tested. Let us therefore start and see how to do a Unit-Test on this configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit Tests
&lt;/h3&gt;

&lt;p&gt;Unit Tests per definition should run without any dependencies like external resources or API calls. In the Terraform world that means that we can test the configuration as long as we do not need any active resources and authentication against providers. This means that everything that parses the configuration can be tested in this stage. In our setup there a two great candidates for the tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The validation logic in the &lt;code&gt;variables.tf&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;The replacement logic of the local values in the &lt;code&gt;main.tf&lt;/code&gt; file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We take the recommendations from the &lt;a href="https://developer.hashicorp.com/terraform/tutorials/configuration-language/test" rel="noopener noreferrer"&gt;Terraform tutorial on testing&lt;/a&gt; as a guidance on how to structure our code.&lt;/p&gt;

&lt;p&gt;Assume that our Terraform configuration is located in a directory &lt;code&gt;infra&lt;/code&gt; we create a new directory &lt;code&gt;tests&lt;/code&gt; as a subdirectory to put in all our tests. The structure looks like this:&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;
| - infra
|   | - main.tf
|   | - variables.tf
|   | - outputs.tf
|   | - provider.tf
|   | - ...
| - tests


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

&lt;/div&gt;

&lt;p&gt;Let us start with testing the input validation. As a first test we want to check if the the variable &lt;code&gt;costcenter&lt;/code&gt; is validated as expected. We create a new file called &lt;code&gt;variables_costcenter_validation.tftest.hcl&lt;/code&gt; in the &lt;code&gt;test&lt;/code&gt; directory. By convention the file name must end with &lt;code&gt;.tftest.hcl&lt;/code&gt; to be recognized by Terraform as a test. &lt;/p&gt;

&lt;p&gt;As we want an isolated test without any connection to the provider we must mock the provider. To do so we add the following code to the test file:&lt;/p&gt;

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

&lt;span class="nx"&gt;mock_provider&lt;/span&gt; &lt;span class="s2"&gt;"btp"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This will make sure that we do not need to authenticate against the provider. Looking at the &lt;code&gt;variables.tf&lt;/code&gt; we see that we have a mandatory parameter that we need to supply namely the &lt;code&gt;globalaccount&lt;/code&gt;. As we mocked the provider, we can define any value for this variable as it will not be used. we add a variable to the file which now looks like this:&lt;/p&gt;

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

&lt;span class="nx"&gt;mock_provider&lt;/span&gt; &lt;span class="s2"&gt;"btp"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;globalaccount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Now we can finally add the test itself. Terraform tests are executed in a so called &lt;code&gt;run&lt;/code&gt; blocks. We add one first block which should test that a valid costcenter is provided:&lt;/p&gt;

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

&lt;span class="nx"&gt;mock_provider&lt;/span&gt; &lt;span class="s2"&gt;"btp"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;globalaccount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"provide_valid_costcenter"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;As you can see we can also specify the &lt;code&gt;command&lt;/code&gt; that should be executed. The validation is already executed in the &lt;code&gt;plan&lt;/code&gt; phase so we added this as &lt;code&gt;command&lt;/code&gt;.&lt;br&gt;
Next we define the valid value of a costcenter in the &lt;code&gt;run&lt;/code&gt; block:&lt;/p&gt;

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

&lt;span class="nx"&gt;mock_provider&lt;/span&gt; &lt;span class="s2"&gt;"btp"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;globalaccount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"provide_valid_costcenter"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;costcenter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"8523652147"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="p"&gt;}&lt;/span&gt;  


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

&lt;/div&gt;

&lt;p&gt;Finally we check if the variable is set as expected via an &lt;code&gt;assert&lt;/code&gt; block that has the typical layout namely a condition and an error message if the condition is not met:&lt;/p&gt;

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

&lt;span class="nx"&gt;mock_provider&lt;/span&gt; &lt;span class="s2"&gt;"btp"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;globalaccount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"provide_valid_costcenter"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;costcenter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"8523652147"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;costcenter&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"8523652147"&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Costcenter is not set correctly"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;  


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

&lt;/div&gt;

&lt;p&gt;Okay, now we also want to provide an invalid value and check if an error is raised. To do so we add another &lt;code&gt;run&lt;/code&gt; block but instead of an &lt;code&gt;assert&lt;/code&gt; block we add an &lt;code&gt;expect_failures&lt;/code&gt; block resulting in:&lt;/p&gt;

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

&lt;span class="nx"&gt;mock_provider&lt;/span&gt; &lt;span class="s2"&gt;"btp"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;globalaccount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"provide_valid_costcenter"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;costcenter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"8523652147"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;costcenter&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"8523652147"&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Costcenter is not set correctly"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"provide_invalid_costcenter_with_letters"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;costcenter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"abc-123"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;expect_failures&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;costcenter&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;  


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

&lt;/div&gt;

&lt;p&gt;Okay, so time to let &lt;code&gt;terraform test&lt;/code&gt; do its magic. In the console we navigate to the &lt;code&gt;infra&lt;/code&gt; directory and initialize the workspace:&lt;/p&gt;

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

terraform init &lt;span class="nt"&gt;-backend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Now we are set and can execute the tests:&lt;/p&gt;

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

terraform &lt;span class="nb"&gt;test&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The output should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0o7he4vkz1n5ra8i5bj4.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%2F0o7he4vkz1n5ra8i5bj4.png" alt="Terraform Test output of first unit test"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This pattern can then be applied to the other validations present in the &lt;code&gt;variables.tf&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;For the sake of exploring further let us switch to the &lt;code&gt;main.tf&lt;/code&gt; file and test the local values. We want to test if the local value &lt;code&gt;project_subaccount_name&lt;/code&gt; is set correctly. We create a new file called &lt;code&gt;local_project_subaccount_domain.tftest.hcl&lt;/code&gt; in the &lt;code&gt;test&lt;/code&gt; directory. We want to check that the local &lt;code&gt;project_subaccount_domain&lt;/code&gt; has no empty spaces in it. &lt;/p&gt;

&lt;p&gt;As before we mock the provider as we just want to test the local values and provide a dummy value for the &lt;code&gt;globalaccount&lt;/code&gt; variable:&lt;/p&gt;

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

&lt;span class="nx"&gt;mock_provider&lt;/span&gt; &lt;span class="s2"&gt;"btp"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;globalaccount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This time we must provide all the variables that are used to construct the local value i.e. &lt;code&gt;org_name&lt;/code&gt;, &lt;code&gt;project_name&lt;/code&gt; and &lt;code&gt;stage&lt;/code&gt;. We add the following variables to the file in a run block:&lt;/p&gt;

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

&lt;span class="nx"&gt;mock_provider&lt;/span&gt; &lt;span class="s2"&gt;"btp"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;globalaccount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"validate_project_subaccount_domain"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;org_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"B2C"&lt;/span&gt;
    &lt;span class="nx"&gt;project_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"proj 1234"&lt;/span&gt;
    &lt;span class="nx"&gt;stage&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DEV"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We intentionally keep an empty space in the &lt;code&gt;project_name&lt;/code&gt; to validate the replacement. We also keep &lt;code&gt;stage&lt;/code&gt; and &lt;code&gt;org_name&lt;/code&gt; in capital letters to validate the transformation to lower case. Next we assert that the value for the local variable is set correctly:&lt;/p&gt;

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

&lt;span class="nx"&gt;mock_provider&lt;/span&gt; &lt;span class="s2"&gt;"btp"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;globalaccount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"validate_project_subaccount_domain"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;org_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"B2C"&lt;/span&gt;
    &lt;span class="nx"&gt;project_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"proj 1234"&lt;/span&gt;
    &lt;span class="nx"&gt;stage&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DEV"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_subaccount_domain&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"b2c-proj-1234-dev"&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Local variable project_subaccount_domain is not transformed correctly."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We can now execute the test as before:&lt;/p&gt;

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

terraform &lt;span class="nb"&gt;test&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We get the following result:&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%2F16t86xuayov1kletl0ms.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%2F16t86xuayov1kletl0ms.png" alt="Terraform test output of second unit test"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great, we saw how we can do unit tests in an isolated way without the need to initialize the provider i.e. to authenticate against a real SAP BTP account. &lt;/p&gt;

&lt;p&gt;As we want to execute the unit tests in a CI/CD pipeline we rename the &lt;code&gt;tests&lt;/code&gt; directory to &lt;code&gt;unit-tests&lt;/code&gt;. When doing so we must the testing framework explicitly to the right directory via the option &lt;code&gt;-test-directory&lt;/code&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="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-test-directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;unit-tests


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

&lt;/div&gt;

&lt;p&gt;This allows us to create a GitHub Action that exclusively executes the unit tests. As the &lt;code&gt;terraform validate&lt;/code&gt; command the &lt;code&gt;terraform test&lt;/code&gt; command can return the test result in a machine readable format namely JSON. This can be used to further parse the test results in a CI/CD pipeline. A basic GitHub Action Workflow that executes the unit tests looks like this:&lt;/p&gt;

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

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Unit Tests&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;opened&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;reopened&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;synchronize&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ready_for_review&lt;/span&gt;
    &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;terraform-validate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate Syntax of Terraform Files&lt;/span&gt;
      &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
      &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
          &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Terraform&lt;/span&gt;
          &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v3&lt;/span&gt;
          &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;terraform_wrapper&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Execute Unit Tests&lt;/span&gt;
          &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
          &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
              &lt;span class="s"&gt;cd ./infra&lt;/span&gt;
              &lt;span class="s"&gt;terraform init -backend=false&lt;/span&gt;
              &lt;span class="s"&gt;terraform test -test-directory=unit-tests &lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We omitted the check for changed files in this workflow. Now let us move on to the next level of testing - the integration tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration Tests
&lt;/h3&gt;

&lt;p&gt;In contrast to the unit test setup, the integration test will interact with a backend and create real resources on the platform. The goal is to check if a given configuration creates a resource successfully. Be aware that this test should focus on your configuration. It makes no sense to test the provider itself with all its parameters. The test of the provider with all its parameters is done via the acceptance tests of the provider.&lt;/p&gt;

&lt;p&gt;Keeping this in mind, we now want to write a test that the subaccount is created successfully by checking the &lt;code&gt;state&lt;/code&gt; field of the newly created subaccount. We do not care about the entitlements, so we will mock the resource.&lt;/p&gt;

&lt;p&gt;First we create a new directory &lt;code&gt;integration-tests&lt;/code&gt; in the &lt;code&gt;infra&lt;/code&gt; directory. In this directory we create a new file called &lt;code&gt;subaccount_creation.tftest.hcl&lt;/code&gt;. As we want to test the resource for the subaccount we switch of the creation of the entitlements by adding a variable with an empty list of entitlements:&lt;/p&gt;

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

&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;entitlements&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Next we add a run block that executes the &lt;code&gt;apply&lt;/code&gt; command and provides the variable values for the subaccount:&lt;/p&gt;

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

&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;entitlements&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"test_successful_subaccount_creation"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;costcenter&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1234567890"&lt;/span&gt;
    &lt;span class="nx"&gt;org_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ECOMMERCE"&lt;/span&gt;
    &lt;span class="nx"&gt;project_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"proj-0815"&lt;/span&gt;
    &lt;span class="nx"&gt;stage&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"TST"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us10"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;With that we can now check for the state of the subaccount via a corresponding &lt;code&gt;assert&lt;/code&gt; block:&lt;/p&gt;

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

&lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;entitlements&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="s2"&gt;"test_successful_subaccount_creation"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt;

  &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;costcenter&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1234567890"&lt;/span&gt;
    &lt;span class="nx"&gt;org_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ECOMMERCE"&lt;/span&gt;
    &lt;span class="nx"&gt;project_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"proj-0815"&lt;/span&gt;
    &lt;span class="nx"&gt;stage&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"TST"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us10"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;btp_subaccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"OK"&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The subaccount was not created in the expected state."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Now we are good to go. In contrast to the unit test we now must provide valid values for the SAP BTP global account. We do so via the command line option &lt;code&gt;-var&lt;/code&gt;. In addition, we must authenticate against the provider, which we will do by setting the corresponding environment variables for &lt;code&gt;BTP_USERNAME&lt;/code&gt; and &lt;code&gt;BTP_PASSWORD&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The command to execute the integration test finally looks like this:&lt;/p&gt;

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

terraform &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-test-directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;integration-tests &lt;span class="nt"&gt;-var&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"globalaccount=yourgasubdomain"&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The execution ow takes a bit longer as the subaccount is created on the SAP BTP. The final result should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F81pd1cyi68gc3rtysm9x.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%2F81pd1cyi68gc3rtysm9x.png" alt="Terraform test output of integration test"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Terraform testing framework will try to remove all resources it created. Nevertheless things could go wrong and some resources might not get destroyed in the teardown of the test. This is clearly displayed in the output of the test, so you closely watch this especially when running the tests in the background via a CI/CD pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where is the code?
&lt;/h3&gt;

&lt;p&gt;You find the code of the previous sections in the GitHub repository &lt;a href="https://github.com/btp-automation-scenarios/terraform-quality" rel="noopener noreferrer"&gt;Terraform Provider for SAP BTP - Quality Aspects&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;In this blog post we learned that we can take the "as-Code" part of the "Infrastructure-as-Code" term seriously. We have seen several options how to safeguard the quality of our Terraform scripts by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checking for proper formatting&lt;/li&gt;
&lt;li&gt;Validating the syntax of the scripts&lt;/li&gt;
&lt;li&gt;Adding Unit and Integration tests leveraging the Terraform testing framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As cherry on top we can automate this in your CI/CD pipeline. The Terraform commands have been created with automation in mind and can return JSON objects that can be used for further action like automatic issue creation.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;terraform test&lt;/code&gt; command and the options of mocking resources and data sources enable a lot more than we have tried out here in this blog post. I highly recommend to take a closer look at the documentation and the blog post referenced before and play around with them. Be aware that this is a quite "young" functionality, so maybe you stumble over issues or might miss some features. If this is the case you definitely should open an issue in the corresponding &lt;a href="https://github.com/hashicorp/terraform" rel="noopener noreferrer"&gt;repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With that ... happy Terraforming and Testing!&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>testing</category>
      <category>infrastructureascode</category>
      <category>sapbtp</category>
    </item>
    <item>
      <title>Giving Kyma a little spin ... a SpinKube</title>
      <dc:creator>Christian Lechner</dc:creator>
      <pubDate>Tue, 09 Apr 2024 07:15:59 +0000</pubDate>
      <link>https://forem.com/lechnerc77/giving-kyma-a-little-spin-a-spinkube-4pmm</link>
      <guid>https://forem.com/lechnerc77/giving-kyma-a-little-spin-a-spinkube-4pmm</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;A few weeks ago I stumbled across a webinar about &lt;a href="https://www.spinkube.dev/"&gt;SpinKube&lt;/a&gt; that was hosted by and showed a presentation how Zeiss IT validated the project based on their requirements and the promises that come along with the project (&lt;a href="https://www.youtube.com/live/BwaXNjzbIeI?si=pcqkUmSvtJatE4zK"&gt;"How ZEISS use Wasm in Enterprise - A special livestream"&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I was impressed and the talk made me curious as the promise that the project "&lt;em&gt;streamlines developing, deploying and operating WebAssembly workloads in Kubernetes - resulting in delivering smaller, more portable applications and incredible compute performance benefits.&lt;/em&gt;" is quite appealing.&lt;/p&gt;

&lt;p&gt;Although I had the project on my radar, I never took a closer look at it. However, now it was the time to give it a spin ... or try. All I needed was a Kubernetes environment. As I am working in the SAP space the choice of Kubernetes is kind of predefined, namely &lt;a href="https://kyma-project.io"&gt;Kyma&lt;/a&gt; in its managed version on the SAP Business Technology Platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Kyma ready for SpinKube
&lt;/h2&gt;

&lt;p&gt;In general, Kyma provides an opinionated but modular stack of Kubernetes resources that should support especially the SAP ecosystem to build extension and applications. It also allows to bring in own components and this way giving the option to tailor the stack according to the needs of your enterprise. In general, bringing the Spin operator into the stack shouldn't be an issue, but as always: the devil lies in the details, so I wanted to try it out.&lt;/p&gt;

&lt;p&gt;In addition, I wanted to check if the Kyma environment on an SAP BTP trial poses some additional constraints on this undertaking, so I decided to use a trial environment for my experiment. Getting a trial environment is straightforward and you find some information about that in the &lt;a href="https://help.sap.com/docs/btp/sap-business-technology-platform/getting-started-with-trial-account-in-kyma-environment"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enabling Kyma
&lt;/h3&gt;

&lt;p&gt;To get started you must first manually enable Kyma in your SAP BTP subaccount. The provisioning takes some time. After that you can access the Kyma console via the URL that is provided in the subaccount overview as well as download the &lt;code&gt;kubeconfig&lt;/code&gt; file that you need to access the cluster via &lt;code&gt;kubectl&lt;/code&gt;. To do so you must have the &lt;code&gt;kubectl&lt;/code&gt; installed on your machine. However, this is not sufficient to access the cluster.&lt;/p&gt;

&lt;p&gt;Authenticating with Kyma is a (in my opinion) unnecessary challenge as it leverages the &lt;a href="https://github.com/int128/kubelogin"&gt;OIDC-login plugin&lt;/a&gt; for kubectl. You find a description of the setup &lt;a href="https://learning.sap.com/learning-journeys/deliver-side-by-side-extensibility-based-on-sap-btp-kyma-runtime/setting-and-configuring-kubectl-for-kyma_b3d25bea-0ef5-498e-bd15-10ef0c23ed06"&gt;here&lt;/a&gt;. This works fine when on a Mac but can give you some headaches on a Windows and on Linux machine especially when combined with restrictive setups in corporate environments. For Windows I can only recommend installing &lt;a href="https://krew.sigs.k8s.io/"&gt;krew&lt;/a&gt; via &lt;a href="https://chocolatey.org/"&gt;chocolatey&lt;/a&gt; and then install the OIDC plugin via &lt;code&gt;kubectl krew install oidc-login&lt;/code&gt;. At least for me that was the only way to get this working on Windows.&lt;/p&gt;

&lt;p&gt;So, we do one extra hop and create a service account that we can use to authenticate with the Kyma cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a service account
&lt;/h3&gt;

&lt;p&gt;To get rid of the OIDC flow and the browser-based login, the next step I did was to create a &lt;em&gt;service account&lt;/em&gt; and a &lt;code&gt;kubeconfig&lt;/code&gt; that contains the necessary roles. The procedure in general is described in a developer tutorial &lt;a href="https://developers.sap.com/tutorials/kyma-create-service-account.html"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Quite some adjustments are necessary as you need access a lot more resources, API groups and verbs to get the Spin operator and its dependencies up and running.&lt;/p&gt;

&lt;p&gt;Let's quickly walk through the steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new namespace for the service account. I created a &lt;code&gt;srvc-account&lt;/code&gt; namespace&lt;/li&gt;
&lt;li&gt;Create the necessary resources (ClusterRole, ClusterRoleBinding, ServiceAccount) in the &lt;code&gt;srvc-account&lt;/code&gt; namespace. This was the fun part as I had to jump from error message stating which role I was missing when setting up SpinKube and its dependencies. This is something you can avoid by using this yaml file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spinkube-service-account&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spinkube-service-account&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes.io/service-account.name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spinkube-service-account&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes.io/service-account-token&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spinkube-role&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;nonResourceURLs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/metrics&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;extensions&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;batch&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apps&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gateway.kyma-project.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;servicecatalog.k8s.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apiextensions.k8s.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node.k8s.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;admissionregistration.k8s.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;coordination.k8s.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cert-manager.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;acme.cert-manager.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;route.openshift.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;certificates.k8s.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;authorization.k8s.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gateway.networking.k8s.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apiregistration.k8s.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;authentication.k8s.io&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apps&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;core.spinoperator.dev&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deployments&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;replicasets&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pods&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pods/log&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pods/portforward&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;jobs&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;configmaps&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apirules&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;serviceinstances&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;servicebindings&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;services&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;secrets&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;customresourcedefinitions&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;runtimeclasses&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;serviceaccounts&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;clusterroles&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;clusterrolebindings&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;roles&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rolebindings&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mutatingwebhookconfigurations&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;validatingwebhookconfigurations&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;namespaces&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;leases&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;secrets&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;issuers&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;issuers/status&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;certificates&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;certificates/finalizers&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;certificates/status&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;certificaterequests&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;certificaterequests/finalizers&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;certificaterequests/status&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;signers&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;clusterissuers&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;clusterissuers/status&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;events&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;challenges&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;challenges/finalizers&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;challenges/status&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;orders&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;orders/finalizers&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;orders/status&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;routes/custom-host&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;certificatesigningrequests&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;certificatesigningrequests/status&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;subjectaccessreviews&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gateways&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gateways/finalizers&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;httproutes&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;httproutes/finalizers&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ingresses&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ingresses/finalizers&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apiservices&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nodes&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;tokenreviews&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deployments/status&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;spinappexecutors&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;spinappexecutors/status&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;spinappexecutors/finalizers&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;spinapps&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;spinapps/status&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;spinapps/finalizers&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;create&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;update&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;patch&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;delete&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;list&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;watch&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deletecollection&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;approve&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sign&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRoleBinding&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spinkube-role-binding&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spinkube-service-account&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;srvc-account&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spinkube-role&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create the &lt;code&gt;kubeconfig&lt;/code&gt; file that you can use to access the cluster with the service account. Depending on your OS you can create a script that fetches the token from the secret and creates the &lt;code&gt;kubeconfig&lt;/code&gt; file. You find the code of the script in step 4 of the &lt;a href="https://developers.sap.com/tutorials/kyma-create-service-account.html"&gt;tutorial&lt;/a&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With this we have the basic setup to provision SpinKube into the Kyma cluster.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - You find all the resources and code snippets in GitHub repository &lt;a href="https://github.com/lechnerc77/spinkube-on-kyma"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Installing SpinKube
&lt;/h3&gt;

&lt;p&gt;The SpinKube documentation provides tutorials to setup SpinKube locally as well as on clusters. There is one specific tutorial for &lt;a href="https://www.spinkube.dev/docs/spin-operator/tutorials/deploy-on-azure-kubernetes-service/"&gt;Azure Kubernetes Service (AKS)&lt;/a&gt; that I used as a reference. Besides the first Azure specific section the SpinKube specific steps are the same for any Kubernetes cluster. Fortunately, Kyma makes no difference here, so I could execute the different steps without any issues.&lt;/p&gt;

&lt;p&gt;From a tooling perspective you need to have &lt;code&gt;kubectl&lt;/code&gt; and &lt;code&gt;helm&lt;/code&gt; installed. I created a devcontainer with these tools installed.&lt;/p&gt;

&lt;p&gt;To give you a rough idea what will be deployed into the Kyma cluster, here is a list of the components that are part of the SpinKube setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy the Spin Operator CRD and the runtime class &lt;a href="https://www.spinkube.dev/docs/spin-operator/tutorials/deploy-on-azure-kubernetes-service/#deploying-the-spin-operator"&gt;link&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Install the cert manager to automatically provision and manage TLS certificates.&lt;/li&gt;
&lt;li&gt;Install the &lt;a href="https://kwasm.sh/"&gt;kwasm&lt;/a&gt; operator via helm&lt;/li&gt;
&lt;li&gt;Install the spin-operator via helm&lt;/li&gt;
&lt;li&gt;Deploy the &lt;a href="https://www.spinkube.dev/docs/glossary/#spin-app-executor-crd"&gt;shim executor&lt;/a&gt; for the Spin application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once you are done the installed components are reflected in the new namespaces:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faxgxv9u4xtk73tjdxq1i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faxgxv9u4xtk73tjdxq1i.png" alt="Kyma Dashboard with SpinKube namespaces" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Time to check if the setup is working.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy the first application
&lt;/h3&gt;

&lt;p&gt;The team behind SpinKube provides some sample apps in their spin operator &lt;a href="https://github.com/spinkube/spin-operator/"&gt;repository&lt;/a&gt; that you can use to test the setup and deploy a simple hello world app. Make sure that you reference the &lt;em&gt;raw&lt;/em&gt; file from GitHub via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/spinkube/spin-operator/main/config/samples/simple.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First we check that the service is available:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gr0e3lhhe5y6zlsiqu4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gr0e3lhhe5y6zlsiqu4.png" alt="SpinKube sample app service" width="800" height="67"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looks good, so after a port-forward via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward services/simple-spinapp 8080:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can access the sample app via curl:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj39uyn4nm985e5niwyne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj39uyn4nm985e5niwyne.png" alt="SpinKube sample app call result via CURL" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So full success, SpinKube is up and running in a Kyma environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  But why ...?
&lt;/h2&gt;

&lt;p&gt;First of all, I wanted to see if there are technical obstacles that the Kyma environment might have that prevent the installation of SpinKube. As you can see there are none even on a trial landscape.&lt;/p&gt;

&lt;p&gt;Besides the technical feasibility the question that remains is: Why would you want to use SpinKube in a Kyma environment? The answer is SpinKube's value proposition mentioned in the beginning: a tool to deploy WebAssembly modules into a Kubernetes environment which can be useful in various scenarios, especially when you want to deploy small, portable, and fast applications. This makes it possible to optimize the utilization of your cluster and/or use smaller clusters for the same workload.&lt;/p&gt;

&lt;p&gt;Of course, the full prove is still not given, as I just deployed a sample app, but the first step is done. Also keep in mind that the SpinKube project is not yet recommended for productive usage, but I think it is a very good point in time to make your first evaluations with it.&lt;/p&gt;

</description>
      <category>spinkube</category>
      <category>kyma</category>
      <category>sap</category>
    </item>
    <item>
      <title>SAP BTP, Terraform and Open Policy Agent</title>
      <dc:creator>Christian Lechner</dc:creator>
      <pubDate>Tue, 02 Apr 2024 06:32:23 +0000</pubDate>
      <link>https://forem.com/lechnerc77/sap-btp-terraform-and-open-policy-agent-243m</link>
      <guid>https://forem.com/lechnerc77/sap-btp-terraform-and-open-policy-agent-243m</guid>
      <description>&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;When demoing the &lt;a href="https://registry.terraform.io/providers/SAP/btp/latest"&gt;Terraform Provider for SAP BTP&lt;/a&gt; and talking about the advantages of Infrastructure as Code, one important aspect is that Terraform does not only provision the infrastructure but covers the &lt;em&gt;management&lt;/em&gt; of the resources like updating configurations. While this is technically straightforward namely you update and apply the configuration, there are some challenges when doing this in a CI/CD setup.&lt;/p&gt;

&lt;p&gt;Let us assume that we have a perfect setup in place: the configurations are stored in a source code management system, and we leverage the a pull-based workflow in the code repository. After a successful review and merge, we have the new configuration in the main branch of the repository and will apply it to the target environment. The application via a CI/CD pipeline from a technical perspective is easy. However, what if we missed a glitch in the new configuration and unfortunately some central resources are getting deleted and created again? Let's say for example an SAP HANA Cloud instance. Oopsie! This would not be received well, I guess. Or rephrasing that in a more graphical way:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwo11ae3habqqfgyggoo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwo11ae3habqqfgyggoo.jpg" alt="Meme: old man drinking coffee" width="500" height="666"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How can we handle this? Are there any mechanisms to prevent or at least to some extent safeguard this kind of issues without falling back to a manual workflow? There is. One huge advantage of sticking to (de-facto) standards like Terraform is that first we are probably not the first ones to come up with this question and second there is a huge ecosystem around Terraform that might help us with such challenges. &lt;br&gt;
And for this specific scenario the solution is the &lt;a href="https://www.openpolicyagent.org/"&gt;&lt;em&gt;Open Policy Agent&lt;/em&gt;&lt;/a&gt;. Let us take a closer look how the solution could look like.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; This topic is not specific to the SAP BTP but is an open topic for Terraform in general. While this blog post focuses on SAP BTP, you can easily exchange the examples with other Terraform providers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;First some words about the Open Policy Agent. The Open Policy Agent (OPA) is a general-purpose policy engine that enables policy enforcement agnostic of the stack you are using. OPA provides a query language called &lt;a href="https://www.openpolicyagent.org/docs/latest/policy-language/"&gt;&lt;em&gt;Rego&lt;/em&gt;&lt;/a&gt;. Citing the official documentation "[...]&lt;em&gt;Rego queries are assertions on data stored in OPA. These queries can be used to define policies that enumerate instances of data that violate the expected state of the system. [...]&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;That sounds like what can help us. Thinking about our Terraform scenario it would be great if we could evaluate the &lt;code&gt;terraform plan&lt;/code&gt; result of the new configuration and check if some unexpected things are happening. If some limits that we define in the policy are exceeded (like number of deleted resources of type XYZ), we would stop the automatic processing via the CI/CD pipeline and let a human look at the setup.&lt;/p&gt;

&lt;p&gt;Luckily this scenario is also covered by a tutorial on the OPA website that you can find &lt;a href="https://www.openpolicyagent.org/docs/latest/terraform/"&gt;here&lt;/a&gt;. The tutorial deals with AWS as an example but making the adjustments for SAP BTP are straightforward. In the following section we will walk through the different components of the solution and see how it works in action.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Components
&lt;/h3&gt;

&lt;p&gt;You find the complete code for this example in the GitHub repository &lt;a href="https://github.com/btp-automation-scenarios/btp-terraform-opa"&gt;https://github.com/btp-automation-scenarios/btp-terraform-opa&lt;/a&gt;. In the following sections take a closer look at the code of the different solution components.&lt;/p&gt;
&lt;h4&gt;
  
  
  The Repository
&lt;/h4&gt;

&lt;p&gt;For this showcase we will use a repository on GitHub to store our code (Terraform as well as the OPA Policy). The layout for the repository is as shown in the screenshot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyeamdyjre6g3vneixuly.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyeamdyjre6g3vneixuly.png" alt="Layout of GitHub repository with sample code" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have a &lt;code&gt;infra&lt;/code&gt; folder that contains our Teraform configuration. The &lt;code&gt;policy&lt;/code&gt; folder contains the OPA policy. And the &lt;code&gt;.github&lt;/code&gt; folder i.e. the &lt;code&gt;workflows&lt;/code&gt; folder therein contains the GitHub Actions configuration for the OPA check run.&lt;/p&gt;

&lt;p&gt;In addition, we will use GitHub Actions to run the CI/CD pipeline. We will look at the configuration in the corresponding section.&lt;/p&gt;
&lt;h4&gt;
  
  
  The Terraform configuration
&lt;/h4&gt;

&lt;p&gt;The basis for the setup is a Terraform configuration that you find in the &lt;code&gt;infra&lt;/code&gt; folder (you find the complete code &lt;a href="https://github.com/btp-automation-scenarios/btp-terraform-opa/tree/main/infra"&gt;here&lt;/a&gt;). We do a very simple setup defined in the main.tf file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# Setup of names in accordance with the company's naming conventions&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;project_subaccount_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;org_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: CF - &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;project_subaccount_domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;org_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;project_subaccount_cf_org&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;org_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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="s2"&gt;"_"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# Creation of subaccount&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"btp_subaccount"&lt;/span&gt; &lt;span class="s2"&gt;"project"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_subaccount_name&lt;/span&gt;
  &lt;span class="nx"&gt;subdomain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_subaccount_domain&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"stage"&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s2"&gt;"costcenter"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;costcenter&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;usage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"NOT_USED_FOR_PRODUCTION"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="c1"&gt;# Assignment of entitlements&lt;/span&gt;
&lt;span class="c1"&gt;###&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"btp_subaccount_entitlement"&lt;/span&gt; &lt;span class="s2"&gt;"entitlements"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entitlement&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entitlements&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;entitlement&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;subaccount_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;btp_subaccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;service_name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;plan_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One subaccount and a list of entitlements are created. The entitlements are defined in the &lt;code&gt;variables.tf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"entitlements"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"List of entitlements for the subaccount."&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"alert-notification"&lt;/span&gt;
      &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"standard"&lt;/span&gt;
      &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SAPLaunchpad"&lt;/span&gt;
      &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"standard"&lt;/span&gt;
      &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hana-cloud"&lt;/span&gt;
      &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hana"&lt;/span&gt;
      &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hana"&lt;/span&gt;
      &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hdi-shared"&lt;/span&gt;
      &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sapappstudio"&lt;/span&gt;
      &lt;span class="nx"&gt;plan&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"standard-edition"&lt;/span&gt;
      &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not a very complex setup, but enough to show the concept. For the sake of this example, we do not even create any resources but will execute the check in the initial setup. Let's move on to the heart of the solution, the OPA policy.&lt;/p&gt;

&lt;h4&gt;
  
  
  Definition of the Policy
&lt;/h4&gt;

&lt;p&gt;We define the policy in the folder &lt;code&gt;policy&lt;/code&gt; as &lt;code&gt;terrraform.rego&lt;/code&gt;. According to the &lt;code&gt;rego&lt;/code&gt; language we define a package and add some basic import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="ow"&gt;package&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;analysis&lt;/span&gt;

&lt;span class="ow"&gt;import&lt;/span&gt; &lt;span class="n"&gt;rego&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;

&lt;span class="ow"&gt;import&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="ow"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tfplan&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This the &lt;code&gt;rego.v1&lt;/code&gt; import is a specific opt-in &lt;a href="https://www.openpolicyagent.org/docs/latest/policy-language/#the-regov1-import"&gt;described here&lt;/a&gt;. In addition, we define the &lt;a href="https://www.openpolicyagent.org/docs/latest/policy-language/#imports"&gt;import&lt;/a&gt; of the Terraform plan (result of the &lt;code&gt;terraform plan&lt;/code&gt; command) as input for the further processing.&lt;/p&gt;

&lt;p&gt;Next, we define some parameters for the policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="c1"&gt;# acceptable score for automated authorization&lt;/span&gt;
&lt;span class="n"&gt;blast_radius&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;

&lt;span class="c1"&gt;# weights assigned for each operation on each resource-type&lt;/span&gt;
&lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"btp_subaccount"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"delete"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"create"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"modify"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"btp_subaccount_entitlement"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"delete"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"create"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"modify"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Consider exactly these resource types in calculations&lt;/span&gt;
&lt;span class="n"&gt;resource_types&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"btp_subaccount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"btp_subaccount_entitlement"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We define a &lt;code&gt;blast radius&lt;/code&gt; that represents an acceptable score for automated execution of the Terraform configuration. To be able to calculate the score we define &lt;code&gt;weights&lt;/code&gt; for each Terraform operation and resource type.&lt;/p&gt;

&lt;p&gt;Now we must calculate the score for the Terraform plan that we provided as input. For that we define some functions that help us to calculate the number of creations, deletions, and modifications for each resource type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="c1"&gt;# list of all resources of a given type&lt;/span&gt;
&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ow"&gt;some&lt;/span&gt; &lt;span class="n"&gt;resource_type&lt;/span&gt;
    &lt;span class="n"&gt;resource_types&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tfplan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_changes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;resource_type&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# number of creations of resources of a given type&lt;/span&gt;
&lt;span class="n"&gt;num_creates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ow"&gt;some&lt;/span&gt; &lt;span class="n"&gt;resource_type&lt;/span&gt;
    &lt;span class="n"&gt;resource_types&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;creates&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"create"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# number of deletions of resources of a given type&lt;/span&gt;
&lt;span class="n"&gt;num_deletes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ow"&gt;some&lt;/span&gt; &lt;span class="n"&gt;resource_type&lt;/span&gt;
    &lt;span class="n"&gt;resource_types&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;deletions&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deletions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# number of modifications to resources of a given type&lt;/span&gt;
&lt;span class="n"&gt;num_modifies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ow"&gt;some&lt;/span&gt; &lt;span class="n"&gt;resource_type&lt;/span&gt;
    &lt;span class="n"&gt;resource_types&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;modifies&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"update"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modifies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is quite some &lt;code&gt;rego&lt;/code&gt; magic going on that you find in the official &lt;a href="https://www.openpolicyagent.org/docs/latest/policy-language/#learning-rego"&gt;documentation&lt;/a&gt;. As you see we are using the &lt;code&gt;tfplan&lt;/code&gt; input to extract the resources and the operations of the Terraform plan to identify the number of create, update, and delete actions.&lt;/p&gt;

&lt;p&gt;Finally, we define the policy itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Authorization holds if score for the plan is acceptable, and no changes are made to IAM&lt;/span&gt;
&lt;span class="ow"&gt;default&lt;/span&gt; &lt;span class="n"&gt;autoexec&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="n"&gt;autoexec&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;blast_radius&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Compute the score for a Terraform plan as the weighted sum of deletions, creations, modifications&lt;/span&gt;
&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
        &lt;span class="ow"&gt;some&lt;/span&gt; &lt;span class="n"&gt;resource_type&lt;/span&gt;
        &lt;span class="n"&gt;crud&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;del&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;crud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;num_deletes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;crud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;num_creates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;mod&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;crud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modify&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;num_modifies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;del&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;mod&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This snippet calculates the overall score and checks if the score is below the defined &lt;code&gt;blast_radius&lt;/code&gt;. If the score is below the threshold the &lt;code&gt;autoexec&lt;/code&gt; "decision" is set to &lt;code&gt;true&lt;/code&gt; and the Terraform plan could be executed automatically. We also have the score available as a "decision" when evaluating the policy.&lt;/p&gt;

&lt;p&gt;You find the code of the policy &lt;a href="https://github.com/btp-automation-scenarios/btp-terraform-opa/blob/main/policy/terraform.rego"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Integration with the CI/CD workflow
&lt;/h4&gt;

&lt;p&gt;We must bring the bits and pieces together. We use a GitHub Actions workflow to run the OPA check. The configuration is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Evaluate Open Policy Agent for Terraform&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sample-proj-opa"&lt;/span&gt;
      &lt;span class="na"&gt;REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Region&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sub&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;account"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eu10"&lt;/span&gt;
      &lt;span class="na"&gt;COST_CENTER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cost&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;center&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1234567890"&lt;/span&gt;
      &lt;span class="na"&gt;STAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stage&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEV"&lt;/span&gt;
      &lt;span class="na"&gt;ORGANIZATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Organization&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B2B"&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PATH_TO_TFSCRIPT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;infra'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;execute_base_setuup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BTP Subaccount Setup&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out Git repository&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout_repo&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Terraform&lt;/span&gt;
      &lt;span class="na"&gt;id &lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;setup_terraform&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v3&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;terraform_wrapper&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;terraform_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Open Policy Agent&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;setup_opa&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;open-policy-agent/setup-opa@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Init&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform_init&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;terraform -chdir=${{ env.PATH_TO_TFSCRIPT }} init -no-color&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform plan&lt;/span&gt; 
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform_plan&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;export BTP_USERNAME=${{ secrets.BTP_USERNAME }}&lt;/span&gt;
        &lt;span class="s"&gt;export BTP_PASSWORD=${{ secrets.BTP_PASSWORD }}&lt;/span&gt;
        &lt;span class="s"&gt;terraform -chdir=${{ env.PATH_TO_TFSCRIPT }} plan -var globalaccount=${{ secrets.GLOBALACCOUNT }} -var region=${{ github.event.inputs.REGION }} -var project_name=${{ github.event.inputs.PROJECT_NAME }} -var stage=${{ github.event.inputs.STAGE }} -var costcenter=${{ github.event.inputs.COST_CENTER }} -var org_name=${{ github.event.inputs.ORGANIZATION }} -no-color --out tfplan.binary&lt;/span&gt;
        &lt;span class="s"&gt;terraform -chdir=${{ env.PATH_TO_TFSCRIPT }} show -json tfplan.binary &amp;gt; tfplan.json&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Execute OPA policy&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;execute_opa&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;autoexec=$(opa exec --decision terraform/analysis/autoexec --bundle policy/ tfplan.json | jq '.result[].result')&lt;/span&gt;
        &lt;span class="s"&gt;score=$(opa exec --decision terraform/analysis/score --bundle policy/ tfplan.json | jq '.result[].result')&lt;/span&gt;
        &lt;span class="s"&gt;echo "Automatic execution possible (true/false): ${autoexec}"&lt;/span&gt;
        &lt;span class="s"&gt;echo "Score of change: ${score}"&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;For the sake of the demo, we use the &lt;code&gt;workflow_dispatch&lt;/code&gt; event to trigger the workflow. The user can provide input parameters for the Terraform configuration. We use the predefined Actions &lt;code&gt;hashicorp/setup-terraform@v3&lt;/code&gt; and &lt;code&gt;open-policy-agent/setup-opa@v2&lt;/code&gt; to setup Terraform as well as OPA. Make sure to set the &lt;code&gt;terraform_wrapper&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; in the Terraform setup to be able to use the &lt;code&gt;terraform&lt;/code&gt; command directly.&lt;/p&gt;

&lt;p&gt;After initializing the Terraform configuration via &lt;code&gt;terraform init&lt;/code&gt; we run the &lt;code&gt;terraform plan&lt;/code&gt; command and store the plan via the &lt;code&gt;-out&lt;/code&gt; parameter as &lt;code&gt;tfplan.binary&lt;/code&gt;. OPA would not be able to work with the plan in this format, so we must convert it to JSON. We do this via the &lt;code&gt;terraform show -json&lt;/code&gt; command and store the plan as &lt;code&gt;tfplan.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we set the stage for the OPA policy execution. We use the &lt;code&gt;opa exec&lt;/code&gt; command to run the policy and provide the Terraform plan as input. To be specific we execute two decisions namely the result of the &lt;code&gt;autoexec&lt;/code&gt; and the &lt;code&gt;score&lt;/code&gt;. The &lt;code&gt;opa exec&lt;/code&gt; would return a JSON object. For the sake of readability, we parse the result via &lt;code&gt;jq&lt;/code&gt; and print the result to the console.&lt;/p&gt;

&lt;p&gt;That's it. What will the result be? Let's see it in action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's see it in action
&lt;/h3&gt;

&lt;p&gt;The execution of the GitHub Action acting looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft60zjbdyisxmow9458ou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft60zjbdyisxmow9458ou.png" alt="Result of GitHub Action run with OPA check" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, the initial application of the configuration would be executed automatically as the score is 15 and below the defined threshold of 30. Let us validate if this is correct:&lt;/p&gt;

&lt;p&gt;We have the creation of the subaccount which counts as 10 points. In addition, we create 5 entitlements which count as 5 points. The total score is 15. The code is working as expected. Great!&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Terraform&lt;/em&gt; is a great tool for provisioning and managing your infrastructure. Automating the corresponding processes is technically quite easy but the management aspects need some more aspects to be considered. The &lt;em&gt;Open Policy Agent&lt;/em&gt; comes in quite handy here as a general-purpose policy engine that can help with overcoming challenges in the automation of the setup. The integration of the tools is quite straightforward as the example of the blog post shows. Not being an expert in the &lt;code&gt;rego&lt;/code&gt; language it is obviously quite powerful, but another topic to learn; and I am honest, I was quite happy to have a running example that I could take from the tutorials area of OPA and just make minor adjustments.&lt;/p&gt;

&lt;p&gt;While the sample I presented here is very simple, it clearly shows how to deal with the challenge of automating your automated infrastructure management.&lt;/p&gt;

&lt;p&gt;With that ... happy Terraforming!&lt;/p&gt;

</description>
      <category>sap</category>
      <category>btp</category>
      <category>terraform</category>
      <category>opa</category>
    </item>
    <item>
      <title>Terraform Provider for SAP BTP - Remote State and Drift Detection</title>
      <dc:creator>Christian Lechner</dc:creator>
      <pubDate>Tue, 26 Mar 2024 08:53:33 +0000</pubDate>
      <link>https://forem.com/lechnerc77/terraform-provider-for-sap-btp-remote-state-and-drift-detection-3pnj</link>
      <guid>https://forem.com/lechnerc77/terraform-provider-for-sap-btp-remote-state-and-drift-detection-3pnj</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In a recent DSAG (German-speaking SAP User Group) event ("DSAG Betriebstage" - can be translated as Ops Days) I had the opportunity to give a talk about Infrastructure as Code (IaC) and how it can help you with governance topics on the SAP Business Technology Platform (BTP). &lt;/p&gt;

&lt;p&gt;One important topic in this context was of course the automated detection of configuration drifts. The scenario basically was that an SAP BTP subaccount was (perfectly) set up using Terraform following all company-specific guidelines. However, a user with the necessary permissions changed the configuration manually in the SAP BTP cockpit. This led to a deviation between the automated configuration and the actual state of the subaccount. This is what we call a &lt;em&gt;configuration drift&lt;/em&gt;. How can we find out about this drift?&lt;/p&gt;

&lt;p&gt;While I showed how this can be detected via Terraform, we did not go into the technical details of the process. To make this theoretical description more tangible we will take a closer look on how we can achieve this from a technical perspective.&lt;/p&gt;

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

&lt;p&gt;To make the following examples work I leveraged the following tools and services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;em&gt;GitHub repository&lt;/em&gt; for storing the Terraform configuration&lt;/li&gt;
&lt;li&gt;An &lt;em&gt;Azure Blob Storage&lt;/em&gt; for centrally storing the Terraform state&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;GitHub Actions&lt;/em&gt; to automate the Terraform workflow especially the drift detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can of course also use different backends like AWS S3. You find a complete list of supported backends in the &lt;a href="https://developer.hashicorp.com/terraform/language/settings/backends/configuration#available-backends"&gt;Terraform documentation&lt;/a&gt;. The same is true for the CI/CD pipeline. You can use e.g., GitLab CI/CD or any other CI/CD tool you prefer to enable the automated flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The GitHub Repository
&lt;/h2&gt;

&lt;p&gt;I created a GitHub repository that you find at &lt;a href="https://github.com/btp-automation-scenarios/btp-terraform-drift"&gt;https://github.com/btp-automation-scenarios/btp-terraform-drift&lt;/a&gt;. This repository contains the Terraform configuration for an SAP BTP subaccount. &lt;/p&gt;

&lt;p&gt;The configuration is very basic and only creates a subaccount with a few entitlements to show the drift detection flow. The infrastructure configuration is stored in the &lt;code&gt;infra&lt;/code&gt; directory of the repository.&lt;/p&gt;

&lt;p&gt;The sensitive information for the setup like username and password is stored in GitHub Secrets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup for Remote State
&lt;/h2&gt;

&lt;p&gt;The detection of a configuration drift relies on the storage of the Terraform state. This state file contains the configuration of the infrastructure and is used by Terraform to determine what must be changed in the infrastructure. &lt;/p&gt;

&lt;p&gt;In a real-life setup this state is stored centrally to make it available to the team of BTP administrators. This can be an Azure Blob Storage, AWS S3 or any other supported backend. One important point here is that the state needs to be encrypted as it might contains sensitive information. In this blog post we will use an Azure Blob Storage.&lt;/p&gt;

&lt;p&gt;The first step is to create such a storage on Microsoft Azure. The Microsoft documentation provides a very good guide on how to setup such a storage account for Terraform state. You find the documentation &lt;a href="https://learn.microsoft.com/azure/developer/terraform/store-state-in-azure-storage?tabs=azure-cli"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the sake of simplicity, we will shortly walk through the steps using the Azure CLI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a resource group on Azure:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   az group create &lt;span class="nt"&gt;--name&lt;/span&gt; rg_terraform_state_sapbtp &lt;span class="nt"&gt;--location&lt;/span&gt; westeurope 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create a storage account in the resource group:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   az storage account create &lt;span class="nt"&gt;--resource-group&lt;/span&gt; rg_terraform_state_sapbtp &lt;span class="nt"&gt;--name&lt;/span&gt; sasapbtptfstate &lt;span class="nt"&gt;--sku&lt;/span&gt; Standard_LRS &lt;span class="nt"&gt;--encryption-services&lt;/span&gt; blob
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create a blob container in the storage account:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   az storage container create &lt;span class="nt"&gt;--name&lt;/span&gt; tfstate &lt;span class="nt"&gt;--account-name&lt;/span&gt; sasapbtptfstate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This results in the following setup for the storage account:&lt;/p&gt;

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

&lt;p&gt;and the blob container (that already contains the state file):&lt;/p&gt;

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

&lt;p&gt;You can also do the setup via the Azure Portal or Terraform.&lt;/p&gt;

&lt;p&gt;Be aware that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure storage comes with &lt;a href="https://learn.microsoft.com/azure/storage/common/storage-service-encryption"&gt;automatic encryption&lt;/a&gt;. You can also use &lt;a href="https://learn.microsoft.com/azure/storage/common/customer-managed-keys-overview?toc=%2Fazure%2Fstorage%2Fblobs%2Ftoc.json&amp;amp;bc=%2Fazure%2Fstorage%2Fblobs%2Fbreadcrumb%2Ftoc.json"&gt;customer managed keys&lt;/a&gt; for that.&lt;/li&gt;
&lt;li&gt;Azure state storage blobs are automatically locked before any operation that writes to the storage account. This prevents concurrent writes to the state file. Details are described in the &lt;a href="https://learn.microsoft.com/azure/developer/terraform/store-state-in-azure-storage?tabs=azure-cli#4-understand-state-locking"&gt;documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, we must make Terraform aware of the fact that should store the state in the Azure Blob Storage. This is done by the following configuration to the &lt;code&gt;provider.tf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;btp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sap/btp"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt;1.1.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"azurerm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;resource_group_name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg_terraform_state_sapbtp"&lt;/span&gt;
    &lt;span class="nx"&gt;storage_account_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sasapbtptfstate"&lt;/span&gt;
    &lt;span class="nx"&gt;container_name&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tfstate"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev.terraform.tfstate"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relevant block is the &lt;code&gt;backend&lt;/code&gt; block. We use a predefined backend provided by Terraform for Azure. The &lt;code&gt;resource_group_name&lt;/code&gt;, &lt;code&gt;storage_account_name&lt;/code&gt; and &lt;code&gt;container_name&lt;/code&gt; are the values we used to define the Azure Blob Storage. The &lt;code&gt;key&lt;/code&gt; is the name of the state file. &lt;/p&gt;

&lt;p&gt;Next, we define the workflows to create and destroy the subaccount.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions for the Setup
&lt;/h2&gt;

&lt;p&gt;To execute the creation and the deletion via GitHub Actions I added two workflows to the repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.github/workflows/setup-subaccount.yml&lt;/code&gt;: this workflow executed the setup of the subaccount in the SAP BTP based on some input variables. The configuration is given by:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Basis Subaccount via Terraform&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sample-proj-drift"&lt;/span&gt;
      &lt;span class="na"&gt;REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Region&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sub&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;account"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eu10"&lt;/span&gt;
      &lt;span class="na"&gt;COST_CENTER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cost&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;center&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1234567890"&lt;/span&gt;
      &lt;span class="na"&gt;STAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stage&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEV"&lt;/span&gt;
      &lt;span class="na"&gt;ORGANIZATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Organization&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B2B"&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PATH_TO_TFSCRIPT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;infra'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;execute_base_setuup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BTP Subaccount Setup&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out Git repository&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout_repo&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Terraform&lt;/span&gt;
      &lt;span class="na"&gt;id &lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;setup_terraform&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v3&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;terraform_wrapper&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;terraform_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Init&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform_init&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;export export ARM_ACCESS_KEY=${{ secrets.ARM_ACCESS_KEY }}&lt;/span&gt;
        &lt;span class="s"&gt;terraform -chdir=${{ env.PATH_TO_TFSCRIPT }} init -no-color&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Apply&lt;/span&gt; 
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform_apply&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;export ARM_ACCESS_KEY=${{ secrets.ARM_ACCESS_KEY }}&lt;/span&gt;
        &lt;span class="s"&gt;export BTP_USERNAME=${{ secrets.BTP_USERNAME }}&lt;/span&gt;
        &lt;span class="s"&gt;export BTP_PASSWORD=${{ secrets.BTP_PASSWORD }}&lt;/span&gt;
        &lt;span class="s"&gt;terraform -chdir=${{ env.PATH_TO_TFSCRIPT }} apply -var globalaccount=${{ secrets.GLOBALACCOUNT }} -var region=${{ github.event.inputs.REGION }} -var project_name=${{ github.event.inputs.PROJECT_NAME }} -var stage=${{ github.event.inputs.STAGE }} -var costcenter=${{ github.event.inputs.COST_CENTER }} -var org_name=${{ github.event.inputs.ORGANIZATION }} -auto-approve -no-color&lt;/span&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.github/workflows/destroy-subaccount.yml&lt;/code&gt;: this workflow destroys the subaccount in the SAP BTP. The configuration is given by:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Destroy Subaccount via Terraform&lt;/span&gt;

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

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PATH_TO_TFSCRIPT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;infra'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;execute_base_setuup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BTP Subaccount Deletion&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out Git repository&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout_repo&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Terraform&lt;/span&gt;
      &lt;span class="na"&gt;id &lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;setup_terraform&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v3&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;terraform_wrapper&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;terraform_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Init&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform_init&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;export export ARM_ACCESS_KEY=${{ secrets.ARM_ACCESS_KEY }}&lt;/span&gt;
        &lt;span class="s"&gt;terraform -chdir=${{ env.PATH_TO_TFSCRIPT }} init -no-color&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Apply&lt;/span&gt; 
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform_apply&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;export ARM_ACCESS_KEY=${{ secrets.ARM_ACCESS_KEY }}&lt;/span&gt;
        &lt;span class="s"&gt;export BTP_USERNAME=${{ secrets.BTP_USERNAME }}&lt;/span&gt;
        &lt;span class="s"&gt;export BTP_PASSWORD=${{ secrets.BTP_PASSWORD }}&lt;/span&gt;
        &lt;span class="s"&gt;terraform -chdir=${{ env.PATH_TO_TFSCRIPT }} destroy -var globalaccount=${{ secrets.GLOBALACCOUNT }} -auto-approve -no-color&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;There is one important thing to note here. The Terraform CLI needs to authenticate not only to SAP BTP but also to Azure to access the Blob storage. I used the storage access key for that. This key is stored in the GitHub Secrets along with the other sensitive information. To make the access key available to Terraform we must export it in the Terraform steps as &lt;code&gt;ARM_ACCESS_KEY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With these GitHub Actions we can setup and destroy the subaccount in the SAP BTP including a central storage of the Terraform state in the Azure Blob Storage. The next step is to automate the detection of a configuration drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drift Detection
&lt;/h2&gt;

&lt;p&gt;The detection of a configuration drift is done by comparing the actual state of the subaccount in the SAP BTP with the state defined in the Terraform configuration. This sounds complicated, but indeed it is quite easy. &lt;/p&gt;

&lt;p&gt;The only thing we need to do is to execute a &lt;code&gt;terraform plan&lt;/code&gt; inside of a GitHub Action. If the stored state matches the configuration on SAP BTP the planning will basically state "nothing to do". In case there is a deviation the planning will come up with changes that it would like to apply to bring the state and the configuration on SAP BTP back in sync. We use this to find out if any unwanted changes have been made.&lt;/p&gt;

&lt;p&gt;To make things easier for the handling in a CI/CD flow we provide an additional parameter to the &lt;code&gt;terraform plan&lt;/code&gt; command namely the &lt;a href="https://developer.hashicorp.com/terraform/cli/commands/plan#detailed-exitcode"&gt;&lt;code&gt;-detailed-exitcode&lt;/code&gt;&lt;/a&gt;. This parameter tells the CLI to provide more granular information about what the resulting plan via the exit code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0 = Succeeded with empty diff, so no drift detected&lt;/li&gt;
&lt;li&gt;1 = Error - this should not happen, but at least good to know that things went completely of the rails&lt;/li&gt;
&lt;li&gt;2 = Succeeded with non-empty diff, so changes are present, and a drift is detected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this we create a new workflow for the drift detection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check for Subaccount Drift via Terraform&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sample-proj-drift"&lt;/span&gt;
      &lt;span class="na"&gt;REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Region&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sub&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;account"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eu10"&lt;/span&gt;
      &lt;span class="na"&gt;COST_CENTER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cost&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;center&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1234567890"&lt;/span&gt;
      &lt;span class="na"&gt;STAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stage&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEV"&lt;/span&gt;
      &lt;span class="na"&gt;ORGANIZATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Organization&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B2B"&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PATH_TO_TFSCRIPT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;infra'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;execute_base_setuup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BTP Subaccount Drift Check&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out Git repository&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout_repo&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Terraform&lt;/span&gt;
      &lt;span class="na"&gt;id &lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;setup_terraform&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v3&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;terraform_wrapper&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;terraform_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Init&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform_init&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;export export ARM_ACCESS_KEY=${{ secrets.ARM_ACCESS_KEY }}&lt;/span&gt;
        &lt;span class="s"&gt;terraform -chdir=${{ env.PATH_TO_TFSCRIPT }} init -no-color&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Plan&lt;/span&gt; 
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform_plan&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
      &lt;span class="na"&gt;continue-on-error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;export ARM_ACCESS_KEY=${{ secrets.ARM_ACCESS_KEY }}&lt;/span&gt;
        &lt;span class="s"&gt;export BTP_USERNAME=${{ secrets.BTP_USERNAME }}&lt;/span&gt;
        &lt;span class="s"&gt;export BTP_PASSWORD=${{ secrets.BTP_PASSWORD }}&lt;/span&gt;
        &lt;span class="s"&gt;terraform -chdir=${{ env.PATH_TO_TFSCRIPT }} plan -var globalaccount=${{ secrets.GLOBALACCOUNT }} -var region=${{ github.event.inputs.REGION }} -var project_name=${{ github.event.inputs.PROJECT_NAME }} -var stage=${{ github.event.inputs.STAGE }} -var costcenter=${{ github.event.inputs.COST_CENTER }} -var org_name=${{ github.event.inputs.ORGANIZATION }} -no-color -detailed-exitcode&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create issue&lt;/span&gt;
      &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.terraform_plan.outcome == 'failure'&lt;/span&gt;  
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v7&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.inputs.PROJECT_NAME }}&lt;/span&gt;
        &lt;span class="na"&gt;REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.inputs.REGION }}&lt;/span&gt;
        &lt;span class="na"&gt;STAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.inputs.STAGE }}&lt;/span&gt;
        &lt;span class="na"&gt;COST_CENTER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.inputs.COST_CENTER }}&lt;/span&gt;
        &lt;span class="na"&gt;RUN_ID &lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.run_id }}&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;const issueTitle = `Configuration Drift Detected for ${process.env.PROJECT_NAME}`&lt;/span&gt;
          &lt;span class="s"&gt;const issueBody = `A drift has been detected for ${process.env.PROJECT_NAME} in ${process.env.REGION} region. Stage is ${process.env.STAGE} and cost center is ${process.env.COST_CENTER}. Find more information in the run https://github.com/btp-automation-scenarios/btp-terraform-drift/actions/runs/${process.env.RUN_ID}`&lt;/span&gt;

          &lt;span class="s"&gt;github.rest.issues.create({&lt;/span&gt;
            &lt;span class="s"&gt;owner: context.repo.owner,&lt;/span&gt;
            &lt;span class="s"&gt;repo: context.repo.repo,&lt;/span&gt;
            &lt;span class="s"&gt;labels: [&lt;/span&gt;
              &lt;span class="s"&gt;'automated issue', 'drift detected'&lt;/span&gt;
            &lt;span class="s"&gt;],&lt;/span&gt;
            &lt;span class="s"&gt;title: issueTitle,&lt;/span&gt;
            &lt;span class="s"&gt;body: issueBody&lt;/span&gt;
          &lt;span class="s"&gt;})&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;State deviation - Set run to failed&lt;/span&gt;
      &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.terraform_plan.outcome == 'failure'&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v7&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;core.setFailed('A configuration drift was detected!')    &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main part of the workflow is the step with the id &lt;code&gt;terraform plan&lt;/code&gt; that executes the planning and provides us the exit code that we use to check if a drift is detected. As follow-up actions we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an issue in the GitHub repository to inform the team about the drift&lt;/li&gt;
&lt;li&gt;Set the run to failed to also make it visible in the GitHub Actions overview&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The step for the creation is defined as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create issue&lt;/span&gt;
      &lt;span class="s"&gt;if&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.terraform_plan.outcome == 'failure'&lt;/span&gt;  
      &lt;span class="s"&gt;uses&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v7&lt;/span&gt;
      &lt;span class="s"&gt;env&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.inputs.PROJECT_NAME }}&lt;/span&gt;
        &lt;span class="na"&gt;REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.inputs.REGION }}&lt;/span&gt;
        &lt;span class="na"&gt;STAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.inputs.STAGE }}&lt;/span&gt;
        &lt;span class="na"&gt;COST_CENTER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.inputs.COST_CENTER }}&lt;/span&gt;
        &lt;span class="na"&gt;RUN_ID &lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.run_id }}&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;const issueTitle = `Configuration Drift Detected for ${process.env.PROJECT_NAME}`&lt;/span&gt;
          &lt;span class="s"&gt;const issueBody = `A drift has been detected for ${process.env.PROJECT_NAME} in ${process.env.REGION} region. Stage is ${process.env.STAGE} and cost center is ${process.env.COST_CENTER}. Find more information in the run https://github.com/btp-automation-scenarios/btp-terraform-drift/actions/runs/${process.env.RUN_ID}`&lt;/span&gt;

          &lt;span class="s"&gt;github.rest.issues.create({&lt;/span&gt;
            &lt;span class="s"&gt;owner: context.repo.owner,&lt;/span&gt;
            &lt;span class="s"&gt;repo: context.repo.repo,&lt;/span&gt;
            &lt;span class="s"&gt;labels: [&lt;/span&gt;
              &lt;span class="s"&gt;'automated issue', 'drift detected'&lt;/span&gt;
            &lt;span class="s"&gt;],&lt;/span&gt;
            &lt;span class="s"&gt;title: issueTitle,&lt;/span&gt;
            &lt;span class="s"&gt;body: issueBody&lt;/span&gt;
          &lt;span class="s"&gt;})&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This step is executed only if the plan state failed which we ensure via the if condition &lt;code&gt;if: steps.terraform_plan.outcome == 'failure'&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;We use the GitHub REST API to create an issue with the corresponding labels. As an example, we provide some additional information, but you can of course also provide more detailed information about the detected drift. &lt;/p&gt;

&lt;p&gt;Make sure that within the settings of the repository (or the organization) the GitHub Actions are allowed to create issues by enabling the "Read and write permissions" for GitHub Actions:&lt;/p&gt;

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

&lt;p&gt;In the current state the workflow would be reported as "successful". I personally prefer that the run is marked as failed in case of a drift. This must be done explicitly by the following step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;State deviation - Set run to failed&lt;/span&gt;
      &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.terraform_plan.outcome == 'failure'&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@v7&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;core.setFailed('A configuration drift was detected!')    &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this we have an automated drift detection in place that informs the team about any deviations between the actual state of the subaccount in the SAP BTP and the configuration defined in the Terraform configuration.&lt;/p&gt;

&lt;p&gt;Once we introduce a drift, we will see the following in the GitHub Actions overview:&lt;/p&gt;

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

&lt;p&gt;As we can see the exit code is as expected a "2" indicating that changes would be applied.&lt;/p&gt;

&lt;p&gt;And consequently, an issue is created in the GitHub repository:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Conclusion and Outlook
&lt;/h2&gt;

&lt;p&gt;With only minor configurations the Terraform built-in functionalities of state storage and planning enable us to detect configuration drifts in the SAP BTP to automatically detect deviations of the configuration with respect to the company-specific guidelines. This ensures that configuration is not changed without the knowledge of the administrator team especially to ensure governance and compliance.&lt;/p&gt;

&lt;p&gt;Now that we have detected the drift, how to deal with it? This usually cannot be handled automatically but needs to be analyzed in detail. You have basically two options that bring things back in sync:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reconcile the state with the configuration in the Terraform configuration. This can be done by executing a &lt;code&gt;terraform apply&lt;/code&gt;. This will apply the necessary changes to the infrastructure and bring it back in sync with the state.&lt;/li&gt;
&lt;li&gt;The manual changes on the platform were okay (e.g., due to an emergency fix). In this case the state file must be updated to reflect the actual state. This can be done by executing a &lt;code&gt;terraform apply&lt;/code&gt; and a &lt;code&gt;terraform apply&lt;/code&gt; with the &lt;code&gt;-refresh-only&lt;/code&gt; flag. This will update the state file with the actual state of the subaccount and not do any changes to the infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another aspect worth to discuss is the integration into existing processes especially on the SAP side of the house. We implemented the flow and the "handling" (namely the creation of a GitHub issue) in an isolated fashion. However, we also received the ask to integrate the flow into the SAP solutions that might already deal with such governance tasks like SAP Cloud ALM. based on that we have a feature request open (&lt;a href="https://github.com/SAP/terraform-provider-btp/issues/747"&gt;link&lt;/a&gt;) in the repository of the &lt;a href="https://github.com/SAP/terraform-provider-btp"&gt;Terraform Provider for SAP BTP&lt;/a&gt;. If you would like to share your point of view or vote for it, this is the place to go.&lt;/p&gt;

&lt;p&gt;With this, nothing more to say than: Happy Terraforming!&lt;/p&gt;

</description>
      <category>sap</category>
      <category>btp</category>
      <category>terraform</category>
      <category>githubactions</category>
    </item>
  </channel>
</rss>
