<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>2 Minute Serverless — Terraform</title>
<link>https://www.serverless.fyi/</link>
<description>Terraform posts from 2 Minute Serverless.</description>
<language>en</language>
<copyright>© Serverless Ventures LLC</copyright>
<generator>serverless-fyi 1.0.0</generator>
<lastBuildDate>Mon, 14 Apr 2025 11:06:44 GMT</lastBuildDate>
<atom:link href="https://www.serverless.fyi/t/terraform/rss.xml" rel="self" type="application/rss+xml"/>
<image><url>https://www.serverless.fyi/brand/icon-192.png</url><title>2 Minute Serverless — Terraform</title><link>https://www.serverless.fyi/</link></image>
<item>
<title>Using Terraform Null Resource vs External Data vs Custom Provider: A Deep Dive</title>
<link>https://www.serverless.fyi/p/using-terraform-null-resource-vs-external-data-vs-custom-provider-a-deep-dive/</link>
<guid isPermaLink="true">https://www.serverless.fyi/p/using-terraform-null-resource-vs-external-data-vs-custom-provider-a-deep-dive/</guid>
<pubDate>Wed, 25 Dec 2024 18:15:46 GMT</pubDate>
<dc:creator>Mohinish Shaikh</dc:creator>
<description>Optimizing Usage Of Scripts in Infrastructure as Code: A Practical Guide</description>
<category>iac</category>
<category>terraform</category>
<content:encoded><![CDATA[<p>During a recent interview, I was asked about the practice of invoking scripts to handle some custom resource management task, lets say creating s3 buckets or ensuring the bucket policies are intact for a set of s3 buckets.</p>
<p>When using Terraform, practitioners often encounter scenarios requiring custom logic that isn’t natively supported by Terraform’s resource providers. One such workaround involves leveraging the <strong>null resource</strong> to invoke a Python script. While this method is simple and quick to implement, alternative approaches such as using the <strong>data external resource</strong> or creating a custom <strong>Terraform provider</strong> offer their own unique advantages. This blog explores these three approaches and compares them based on use cases, pros, and cons.</p>
<p>Thus integrating Python scripts with Terraform, there are three main approaches: null_resource with local-exec provisioner, data "external" blocks, and custom providers.</p>
<p>Let's analyze each approach and their optimal use cases.</p>
<h2>Null Resource with Python Script</h2>
<p><a href="https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource">Null resources</a> in Terraform act as placeholder resources that don't create any real infrastructure but allow you to trigger provisioners and execute arbitrary commands, making them ideal for running Python scripts during Terraform operations.</p>
<pre><code class="language-hcl">resource "null_resource" "python_executor" {
  provisioner "local-exec" {
    command = "python script.py ${var.input_param}"
  }
}
</code></pre>
<p>The <a href="https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource">null_resource</a> approach executes Python scripts as local commands during Terraform's apply phase. This is straightforward but has limitations in state management and data return handling.</p>
<h3>Pros:</h3>
<ol>
<li>Quick to implement with minimal setup.</li>
<li>No need for additional tools or plugins.</li>
<li>Ideal for small, one-off customizations.</li>
</ol>
<h4>Cons:</h4>
<ol>
<li>Tightly coupled to the execution environment, requiring dependencies (e.g., Python) to be installed.</li>
<li>Difficult to scale and maintain as complexity grows.</li>
<li>Error handling and state management are rudimentary.</li>
</ol>
<hr>
<h2>External Data Source</h2>
<p>The <a href="https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external">external data source</a> in Terraform provides a standardized interface for executing external programs and incorporating their output into Terraform's state management system, requiring the external program to accept JSON input and return JSON output in a specific format.</p>
<pre><code class="language-hcl">data "external" "python_data" {
  program = ["python", "script.py"]
  query = {
    param = var.input_param
  }
}
</code></pre>
<p>The <a href="https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external">external data source</a> provides a structured way to execute external programs and capture their output in Terraform's state.</p>
<h3>Pros:</h3>
<ol>
<li>Provides a cleaner interface for input and output handling.</li>
<li>Decouples execution logic from Terraform resources.</li>
<li>Output can be used dynamically in other Terraform resources.</li>
</ol>
<h4>Cons:</h4>
<ol>
<li>Slightly more setup compared to <code>null_resource</code>.</li>
<li>Limited to simple input-output tasks.</li>
<li>Performance can be a concern for long-running scripts.</li>
</ol>
<hr>
<h2>Custom Provider</h2>
<p><a href="https://www.hashicorp.com/blog/writing-custom-terraform-providers">Custom providers</a> in Terraform allow you to encapsulate complex business logic into a dedicated, maintainable Go package that integrates natively with Terraform's plugin system, offering full state management, validation, and resource lifecycle control through a well-defined API.</p>
<pre><code class="language-hcl">provider "custom" {
  // Configuration options
}

resource "custom_resource" "example" {
  input_param = var.input_param
}
</code></pre>
<p>Creating a <a href="https://www.hashicorp.com/blog/writing-custom-terraform-providers">custom provider</a> requires more initial setup but offers the best integration with Terraform's ecosystem.</p>
<h3>Pros:</h3>
<ol>
<li>Highly scalable and maintainable.</li>
<li>Offers first-class integration with Terraform workflows.</li>
<li>Well-suited for complex, reusable logic.</li>
</ol>
<h4>Cons:</h4>
<ol>
<li>Requires significant time and expertise to develop.</li>
<li>Written in Go, not Python.</li>
<li>Overhead might be unnecessary for simple use cases.</li>
</ol>
<h2>Comparision</h2>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>Null Resource</th>
<th>External Data</th>
<th>Custom Provider</th>
</tr>
</thead>
<tbody>
<tr>
<td>Setup Complexity</td>
<td>Low - Simple script execution</td>
<td>Medium - Requires JSON I/O handling</td>
<td>High - Requires Go development</td>
</tr>
<tr>
<td>State Management</td>
<td>Poor - No built-in state tracking</td>
<td>Good - State managed via data source</td>
<td>Excellent - Full state management</td>
</tr>
<tr>
<td>Error Handling</td>
<td>Basic - Exit codes only</td>
<td>Moderate - JSON response validation</td>
<td>Robust - Native error handling</td>
</tr>
<tr>
<td>Reusability</td>
<td>Limited - Script-dependent</td>
<td>Moderate - Reusable across configurations</td>
<td>Excellent - Distributable as package</td>
</tr>
<tr>
<td>Development Time</td>
<td>Quick - Immediate implementation</td>
<td>Medium - JSON interface needed</td>
<td>Long - Provider development required</td>
</tr>
<tr>
<td>Maintenance</td>
<td>Challenging - Manual script updates</td>
<td>Moderate - Version control needed</td>
<td>Simple - Managed via registry</td>
</tr>
<tr>
<td>Input/Output Control</td>
<td>Limited - Environment variables/CLI</td>
<td>Good - Structured JSON</td>
<td>Excellent - Schema-validated</td>
</tr>
<tr>
<td>Testing</td>
<td>Difficult - Manual testing required</td>
<td>Moderate - JSON testing possible</td>
<td>Excellent - Built-in testing framework</td>
</tr>
<tr>
<td>Documentation</td>
<td>Manual - Separate documentation needed</td>
<td>Basic - Data source docs</td>
<td>Excellent - Registry documentation</td>
</tr>
<tr>
<td>Version Control</td>
<td>Manual - Script versioning needed</td>
<td>Good - Script + configuration</td>
<td>Excellent - Provider versioning</td>
</tr>
</tbody>
</table>
<p>Choose based on your requirements:</p>
<ul>
<li>
<p>Use null_resource for simple, one-off script executions</p>
<ul>
<li>Personally, I would try to avoid the usage of scripts since it introduces security and governance risk with systems where they get invoked.</li>
</ul>
</li>
<li>
<p>Use external data for structured data exchange without complex logic</p>
<ul>
<li>Personally, I would use these for limited use cases.</li>
</ul>
</li>
<li>
<p>Develop a custom provider for production-grade, reusable infrastructure components</p>
<ul>
<li>This is the best approach but requires heavy lifting.</li>
</ul>
</li>
</ul>
<p>The choice ultimately depends on factors like development time, maintenance requirements, and reusability needs.</p>]]></content:encoded>
</item>
</channel>
</rss>
