| title | sub_title | author | event | location | date | options | theme | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Let's build your own OpenTofu provider | Introduction to OpenTofu / Terraform Provider Development | Konstantin Ignatov | Gophercamp.cz 2025 | Clubco, Brno, Czech Republic | Friday, April 25, 2025 | 
 | 
 | 
This repository contains the materials for the workshop on creating OpenTofu (Terraform) providers.
In this workshop, I’ll guide you through the fundamentals of provider development, culminating in building a minimalistic but functional provider with custom logic. I’ll highlight various features of the relevant SDKs.
Each .md file is a presentation, and the one you’re reading now is the
introduction to the topic.
The presentations are built for
presenterm and are meant to be run
in a modern terminal such as Ghostty or
kitty.
If you’ve scanned the QR code with the repo URL, you might want to skip to Getting started.
Each exercise has a separate presentation file, and they are numbered.
If you get stuck, feel free to check out the solutions provided in the same
repo. You’ll find the folders NN-solution, where NN is the exercise number.
Good luck!
- Scenario: Working on a payment system
- Multiple payment providers
- Various payment routes (through the providers)
- Different rules when to use which provider
- Rules scattered across files, DBs, and switches in external services
- Ensuring complete coverage (all currencies, card types) is complex
- Rules depend on each other: easy to create conflicts
Guess what happened with the system over time, and how everything was working in reality?
%%{
  init: {
    'themeVariables': {
        'fontFamily': 'JetBrainsMonoNL NFM Bold, Fira Code Bold, monospace',
    }
  }
}%%
sequenceDiagram
    actor       User
    participant App
    participant API as Remote service
    User                ->>+  App:    Change things for me
    loop    Until states match
        App             ->>+  API:    What's the current state?
        API             ->>-  App:    Here's the current state
        Note over App:  Comparing states,<br />What changes are needed?
        App             ->>+  API:    Make these changes
        Note over API: Applying changes
        API             ->>-  App:    Changes applied
    end
    App                 ->>-  User:   All done! #10004;
    This manual loop is often slow and error-prone.
- What happens when someone changes a resource outside your system? (Drift!)
- What happens when it fails halfway through? (Inconsistent state!)
- How do you handle dependencies between resources? (You didn't expect you'd need graph algorithms and topological sort, didn't you?)
%%{
  init: {
    'themeVariables': {
        'fontFamily': 'JetBrainsMonoNL NFM Bold, Fira Code Bold, monospace',
    }
  }
}%%
sequenceDiagram
    actor         Developer
    participant   TF        as Terraform/OpenTofu
    participant   API       as Cloud Provider API
    Developer ->> TF:         tofu init
    TF        ->> Developer:  Providers downloaded
    Developer ->> TF:         tofu plan
    TF        ->> API:        Check current state
    API       ->> TF:         Calculate changes
    TF        ->> Developer:  Show execution plan
    No changes yet!
Use the plan:
%%{
  init: {
    'themeVariables': {
        'fontFamily': 'JetBrainsMonoNL NFM Bold, Fira Code Bold, monospace',
    }
  }
}%%
sequenceDiagram
    actor         Developer
    participant   TF        as Terraform/OpenTofu
    participant   API       as Cloud Provider API
    participant   Infra     as Infrastructure
    Developer ->> TF:         tofu apply
    TF        ->> API:        Create/Update resources
    API       ->> Infra:      Provision resources
    Infra     ->> API:        Confirm changes
    API       ->> TF:         Return results
    TF        ->> Developer:  Show completion summary
    No loops!
| Feature | Build OpenTofu Provider | Without OpenTofu / Terraform | 
|---|---|---|
| Go Framework | terraform-plugin-{go,sdk} | ✅ Anything you like! | 
| Error Handling | ||
| Error Reporting | ✅ Framework diagnostics | ❌ Implementation effort | 
| Object Diff | ✅ HCL / SDK ↔ Go helpers | ❌ Implementation effort | 
| Dependencies | ✅ Handled by OpenTofu | ❌ Implementation effort | 
| Testing Harness | ✅ Testing utilities | ❌ Custom test harness | 
| Timeouts | ✅ Built-in | ❌ Implementation effort | 
| Locking (state) | ✅ Built-in | ❓ Can be done | 
| CI/CD | ✅ Supported | ❌ Implementation effort | 
| Visual edit / GUI | ❌ No, only file editing | ✅ Can be done | 
| Visual diff | ✅ Built-in | ❌ Implementation effort | 
| Idempotency | ✅ Built-in | ❓ Can be done | 
| Drift detection | ✅ Built-in | ❌ Implementation effort | 
| Partial updates | ⭕️ Supported | ❌ Manual work | 
| State history | ✅ Pluggable | ❓ Can be done | 
| Transactions | ❌ No, Unsupported | ❓ Can be done. Maybe | 
| Rollback | ❌ Implementation effort | 
- Complex State & Dependencies: managing resources with intricate states or interdependencies. Leverage TF Core's graph management.
- Collaboration: Multiple users manage config. Benefit from built-in state locking and drift detection.
- Standard IaC Practices: Reuse existing Infrastructure as Code workflows, tooling, and CI/CD pipelines.
- Declarative Approach: Defining the desired end state is natural and idempotency is crucial. The framework encourages this.
- Very Simple State Model: Managing items with minimal dependencies or complexity.
- Specific UI/UX Needs: Interactions beyond config files are necessary, like a dedicated GUI.
- Atomic Transactions: Changes must succeed or fail as a single, indivisible unit. OpenTofu doesn't offer this.
- Inherently Imperative Tasks: The core logic involves sequential steps or procedures (e.g., complex data migrations).
- Overriding Tech Constraints: Must use non-Go languages/frameworks or the plugin architecture is unsuitable.
OpenTofu is a community-driven fork of Terraform.
| OpenTofu | Terraform | |
|---|---|---|
| Licensing | MPL 2.0 | BUSL-1.1 | 
| Copyright | The OpenTofu Authors | HashiCorp, Inc. | 
| Maintaining organization | Linux Foundation | HashiCorp, Inc. | 
| Governance | Community-driven | Corporate-driven | 
Provider Development Frameworks (as of now, all MPL and HashiCorp):
| Repo / Import | |
|---|---|
| High-level (modern) | github.com/hashicorp/terraform-plugin-framework | 
| High-level (previous) | github.com/hashicorp/terraform-plugin-sdk | 
| Low-level (active) | github.com/hashicorp/terraform-plugin-go | 
A Provider is the plugin that teaches OpenTofu how to interact with a specific target API or platform (e.g., AWS, Kubernetes, a custom service).
Under the hood:
- Provider is an executable binary
- It's usually written in Go (e.g., using terraform-provider-framework).
- Distributed via OpenTofu Registry or Terraform Registry.
- OpenTofu runs it and communicates with it via gRPC.
- OpenTofu tells the provider what to create, update and delete
- THe provider calls relevant APIs.
Key Responsibilities:
- Define Schema: Resources, data sources, functions, and the attributes.
- Authenticate: Manages credentials for the target API.
- Manage Resources: Implements the Create, Read, Update, Delete logic.
| Core | Provider | 
|---|---|
| Configuration parsing/interpolation | API client implementation | 
| Dependency graph construction | Resource schema definition | 
| State file management | CRUD operations | 
| Plan/apply workflow execution | Authentication handling | 
| Plugin RPC communication | Error translation | 
flowchart LR
  Core[OpenTofu Core]
  Core     -->|RPC| Provider
  Core     -->      State[State Management]
  Core     -->      Plan[Execution Plans]
  Provider -->      API[API Calls]
  Provider -->      Resource[Resource CRUD]
    HashCorp Configuration Language:
- Human-readable configuration language
- Declarative syntax
- Key-value pairs, blocks, lists
- Used to define infrastructure resources
HCL vs JSON (and alike):
- Comments
- Interpolation
- Functions
- Variables
- References
- Expressions
HCL with a schema can be mapped to JSON and back, with a couple of twists:
- A block in HCL can be either an object or a list of objects, or a set of objects.
- Schema (per provider and global — for .tffiles themselves) is needed to distinguish that
OpenTofu JSON Configuration Syntax
JSON:
{
  "output": {
    "bar": {
      "value": "${aws_instance.foo}"
    }
  }
}Terraform:
output "bar" {
  value = aws_instance.foo
}So, it's not straightforward (e.g., note $ sign in the JSON variant),
but it's a way to think about HCL, and you can use JSON syntax to generate
resource terraform definitions.
mindmap
  root((HCL))
    Blocks
        Provider
          ::icon(fa fa-plug)
        Resource
          ::icon(fa fa-cubes)
        Data
          ::icon(fa fa-database)
        Module
        Variable
        Output
        Locals
        Terraform
        Moved
        Import
        Check
        Backend
        Required_Providers
    Expressions
      Literals
      References
      Functions
      Operators
      Conditionals
      For Expressions
      Dynamic Blocks
      Splat Expressions
      Indexing/Slicing
      Heredoc Syntax
    Attributes
      Types
        String
        Number
        Bool
        List
        Map
        Object
        Set
        Null
        Any
    Meta-Arguments
      count
      for_each
      depends_on
      provider
      lifecycle
        prevent_destroy
        create_before_destroy
        ignore_changes
        replace_triggered_by
      provisioner
      connection
      ["alias (provider)"]
    Once you have your provider developed, you can use the full power of HCL and
Terraform.
For provider, implement only:
- Resource Blocks
- Data Blocks
- Provider Block
Provider
provider "foo" {
  provider_attribute = "value"
  provider_block {
    nested_attribute = 1 + 2
  }
}Data Source
// "foo" is the provider name
data "foo_bar" "hello" {
  data_attribute = true
  data_source_block {
    attribute = "${expression}"
  }
}Provider Function
// assign to some attribute:
x = provider::foo::myfunc(1, 2)Resource
resource "foo_baz" "yep" {
  ref = data.foo_bar.computed_v
  all = [0, 1, 2]
}import {
	"github.com/hashicorp/terraform-plugin-framework/datasource"
	"github.com/hashicorp/terraform-plugin-framework/function"
	"github.com/hashicorp/terraform-plugin-framework/provider"
	"github.com/hashicorp/terraform-plugin-framework/resource"
}
type PlaygroundProvider struct {}
type PlaygroundDataSource struct {}
type PlaygroundResource struct {}
type PlaygroundFunction struct {}
var _ provider.Provider     = (*PlaygroundProvider)(nil)
var _ datasource.DataSource = (*PlaygroundDataSource)(nil)
var _ provider.Resource     = (*PlaygroundResource)(nil)
var _ function.Function     = (*PlaygroundFunction)(nil)…and you just follow compiler errors telling you what is still missing
| Feature | Provider | Datasource | Resource | Function | 
|---|---|---|---|---|
| Schema | ✅ | ✅ | ✅ | ✅ Definition+Return | 
| Configure | ✅ | ✅ | ✅ | ❌ OpenTofu only, low-level | 
| Read | ❌ | ✅ | ✅ | ✅ Stateless Run() | 
| Create | ❌ | ❌ | ✅ | ❌ | 
| Update | ❌ | ❌ | ✅ | ❌ | 
| Delete | ❌ | ❌ | ✅ | ❌ | 
| Import | ❌ | ❌ | ✅ | ❌ | 
Alternatives to start learning:
With low-level SDK:
Now that you have an overview of OpenTofu/Terraform provider concepts, it's time to dive into the hands-on exercises!
- Navigate to the First Exercise: The workshop modules are in numbered
markdown files (NN-*.md). Begin with the first one.
- Follow the Sequence: Proceed through the exercises in numerical order
(01-functions.md,02-flags-and-attrs.md, etc.).
- Check Solutions: If you get stuck or want to compare your code,
solutions for each exercise NNare available in the correspondingNN-solution/directory.
Let's get coding!
█ ▄▄▄▄▄ ██▀▄██▀▄██▄  █▄▄ ▀█ ▄▄▄▄▄ █
█ █   █ █▄▀█▄▀▀█▄██▀  █▀███ █   █ █
█ █▄▄▄█ ██▄▀▀ ▄▀▀▄█▀ █▀█ ▄█ █▄▄▄█ █
█▄▄▄▄▄▄▄█ █▄▀▄█▄▀▄▀▄▀▄▀▄█ █▄▄▄▄▄▄▄█
█▄▄▄▀▄ ▄▄ ▀█▀ ▀██▀▄██▀ ▀█   ██▀▄█▀█
█ ▄ ██ ▄▀▀▀ ▄ ▀ █▀▀ ▀▀▀▄█▀█ █▀ █ ▄█
█▄▄   ▀▄▀ █  ██ ▄  █▄▄▄▄▄   ▀█ ▀█ █
█▄▀█▄ █▄██  ▀█▄█▀▄▀█▀▀ ▀▀▀▄▀▀█▀█ ▄█
██▀▄█▄ ▄█ █▄▀ ▀█▄  █▄  ▀▄▄  ███▀▄ █
█   █▀ ▄█▀▀█▄ ▀ ██▀▀█▄▀█▀▀▄█▄▄ █ ▄█
██▄▀▄█ ▄ ▀█  ██ █▀ ▀█▀ ██ ▀ ▄██▀▄ █
█▄▀▀█ ▄▄ ▀█▀▀█▄███ ▄█▀▀▀▀ ▄▀▀ ▄█ ▄█
█▄█▄█▄▄▄▄▀▀▄▀ ▀█▄ ▄▄▄  ▀▀ ▄▄▄ ██▀▀█
█ ▄▄▄▄▄ █ █ ▄ ▀ █▄▀▀█  ▄▄ █▄█ ▄█  █
█ █   █ █▄▀█ ██ █▀ ▀█  ▀█ ▄▄  ▀▀█▀█
█ █▄▄▄█ █ ▀ ▀█▄██▀ ▀▀█ ▄ ▀▀█ ▄▄▄▀▄█
█▄▄▄▄▄▄▄█▄█▄█▄██▄▄▄██▄▄█▄▄▄█▄▄▄█▄▄█