Skip to content

Terraform_provider_development

Creating custom Terraform providers allows you to manage resources that aren’t supported by existing providers or to create abstractions for internal infrastructure.

  • Support custom APIs: Manage internal systems
  • Abstraction: Simplify complex configurations
  • Reuse: Share infrastructure patterns
  • Internal tools: Integrate with company tools
my_provider/
├── go.mod
├── main.go
├── provider.go
├── resource/
│ └── resource_example.go
├── data_source/
│ └── data_source_example.go
└── docs/
└── index.md
provider.go
package main
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
)
func Provider() *schema.Provider {
return &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"my_resource": resourceExample(),
},
DataSourcesMap: map[string]*schema.Resource{
"my_data_source": dataSourceExample(),
},
}
}
func main() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: func() *schema.Provider {
return Provider()
},
})
}
resource/resource_example.go
package main
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func resourceExample() *schema.Resource {
return &schema.Resource{
CreateContext: resourceExampleCreate,
ReadContext: resourceExampleRead,
UpdateContext: resourceExampleUpdate,
DeleteContext: resourceExampleDelete,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"config": {
Type: schema.TypeString,
Required: true,
},
"tags": {
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}
func resourceExampleCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
// Get API client
client := m.(*APIClient)
// Extract data
name := d.Get("name").(string)
config := d.Get("config").(string)
tags := d.Get("tags").(map[string]string)
// Call API to create resource
result, err := client.CreateResource(name, config, tags)
if err != nil {
return diag.FromErr(err)
}
// Set ID
d.SetId(result.ID)
return resourceExampleRead(ctx, d, m)
}
func resourceExampleRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := m.(*APIClient)
id := d.Id()
result, err := client.GetResource(id)
if err != nil {
// Handle not found
d.SetId("")
return nil
}
d.Set("name", result.Name)
d.Set("config", result.Config)
d.Set("tags", result.Tags)
return nil
}
func resourceExampleUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := m.(*APIClient)
id := d.Id()
if d.HasChange("name") || d.HasChange("config") || d.HasChange("tags") {
name := d.Get("name").(string)
config := d.Get("config").(string)
tags := d.Get("tags").(map[string]string)
_, err := client.UpdateResource(id, name, config, tags)
if err != nil {
return diag.FromErr(err)
}
}
return resourceExampleRead(ctx, d, m)
}
func resourceExampleDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := m.(*APIClient)
id := d.Id()
err := client.DeleteResource(id)
if err != nil {
return diag.FromErr(err)
}
d.SetId("")
return nil
}
data_source/data_source_example.go
package main
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceExample() *schema.Resource {
return &schema.Resource{
ReadContext: dataSourceExampleRead,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"result": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func dataSourceExampleRead(d *schema.ResourceData, m interface{}) error {
client := m.(*APIClient)
name := d.Get("name").(string)
result, err := client.QueryData(name)
if err != nil {
return err
}
d.SetId(name)
d.Set("result", result)
return nil
}
provider.go
func Provider() *schema.Provider {
return &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"my_resource": resourceExample(),
},
DataSourcesMap: map[string]*schema.Resource{
"my_data_source": dataSourceExample(),
},
ConfigureContextFunc: func(ctx context.Context, d *schema.ResourceData) (interface{}, error) {
// Get API key from config
apiKey := d.Get("api_key").(string)
// Create client
client, err := NewAPIClient(apiKey)
if err != nil {
return nil, err
}
return client, nil
},
Schema: map[string]*schema.Schema{
"api_key": {
Type: schema.TypeString,
Required: true,
Description: "API key for the service",
},
"api_url": {
Type: schema.TypeString,
Optional: true,
Default: "https://api.example.com",
Description: "API URL",
},
},
}
}
package main
import (
"testing"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func TestAccResourceExample(t *testing.T) {
// Acceptance test requires real API
t.Skip("Skipping acceptance test")
}
func TestResourceExampleName(t *testing.T) {
// Test schema
resource := resourceExample()
if resource.Schema["name"] == nil {
t.Fatal("name schema is required")
}
if !resource.Schema["name"].Required {
t.Fatal("name must be required")
}
}
func TestFlattenConfig(t *testing.T) {
// Test helper functions
input := map[string]interface{}{
"key1": "value1",
"key2": "value2",
}
output := flattenConfig(input)
if output["key1"] != "value1" {
t.Errorf("expected value1, got %s", output["key1"])
}
}
func TestAccResourceExample(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccResourceExampleDestroy,
Steps: []resource.TestStep{
{
Config: testAccResourceExampleConfig(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("my_resource.example", "name", "test"),
resource.TestCheckResourceAttr("my_resource.example", "config", "value"),
),
},
},
})
}
func testAccResourceExampleConfig() string {
return `
resource "my_resource" "example" {
name = "test"
config = "value"
tags = {
env = "test"
}
}
`
}
docs/resources/example.md
layout: ""
page_title: "my_resource"
description: |-
Manages example resource.
---
# Resource: my_resource
Manages example resource in the system.
## Example Usage
```hcl
resource "my_resource" "example" {
name = "example"
config = "configuration"
tags = {
env = "production"
}
}
  • name - (Required) Name of the resource
  • config - (Required) Configuration for the resource
  • tags - (Optional) Tags to apply to the resource
  • id - ID of the resource
  • created_at - Creation timestamp
## Building and Publishing
```bash
# Build provider
go build -o terraform-provider-myprovider
# Install locally
mkdir -p ~/.terraform.d/plugins/example.com/namespace/myprovider/1.0.0/linux_amd64
cp terraform-provider-myprovider ~/.terraform.d/plugins/example.com/namespace/myprovider/1.0.0/linux_amd64/
# Publish to Terraform Registry
# Push to GitHub with appropriate tags
git tag v1.0.0
git push origin v1.0.0

Provider development enables:

  • Custom resources: Manage non-standard resources
  • Abstractions: Simplify complex configurations
  • Internal systems: Integrate with company tools
  • Sharing: Distribute reusable infrastructure

Key concepts:

  • schema.Resource: Define CRUD operations
  • schema.Provider: Register resources and data sources
  • Context functions: Use context for timeouts/cancellation
  • Testing: Write unit and acceptance tests