Terraform_provider_development
Terraform Provider Development
Section titled “Terraform Provider Development”Overview
Section titled “Overview”Creating custom Terraform providers allows you to manage resources that aren’t supported by existing providers or to create abstractions for internal infrastructure.
Why Develop a Provider?
Section titled “Why Develop a Provider?”- Support custom APIs: Manage internal systems
- Abstraction: Simplify complex configurations
- Reuse: Share infrastructure patterns
- Internal tools: Integrate with company tools
Provider Structure
Section titled “Provider Structure”my_provider/├── go.mod├── main.go├── provider.go├── resource/│ └── resource_example.go├── data_source/│ └── data_source_example.go└── docs/ └── index.mdBasic Provider Structure
Section titled “Basic Provider Structure”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 Implementation
Section titled “Resource Implementation”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 Implementation
Section titled “Data Source Implementation”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 Configuration
Section titled “Provider Configuration”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", }, }, }}Testing Providers
Section titled “Testing Providers”Unit Tests
Section titled “Unit Tests”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"]) }}Acceptance Tests
Section titled “Acceptance Tests”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" }}`}Documentation
Section titled “Documentation”layout: ""page_title: "my_resource"description: |- Manages example resource.---
# Resource: my_resource
Manages example resource in the system.
## Example Usage
```hclresource "my_resource" "example" { name = "example" config = "configuration"
tags = { env = "production" }}Argument Reference
Section titled “Argument Reference”name- (Required) Name of the resourceconfig- (Required) Configuration for the resourcetags- (Optional) Tags to apply to the resource
Attributes Reference
Section titled “Attributes Reference”id- ID of the resourcecreated_at- Creation timestamp
## Building and Publishing
```bash# Build providergo build -o terraform-provider-myprovider
# Install locallymkdir -p ~/.terraform.d/plugins/example.com/namespace/myprovider/1.0.0/linux_amd64cp terraform-provider-myprovider ~/.terraform.d/plugins/example.com/namespace/myprovider/1.0.0/linux_amd64/
# Publish to Terraform Registry# Push to GitHub with appropriate tagsgit tag v1.0.0git push origin v1.0.0Summary
Section titled “Summary”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