---
title: "Go e Terraform: Infrastructure as Code com Go"
url: "https://golang.com.br/tutoriais/go-terraform/"
markdown_url: "https://golang.com.br/tutoriais/go-terraform.MD"
description: "Aprenda a criar providers Terraform personalizados em Go para automatizar sua infraestrutura. Guia completo com exemplos práticos e padrões de produção."
date: "2026-02-11"
author: "Hugo"
---

# Go e Terraform: Infrastructure as Code com Go

Aprenda a criar providers Terraform personalizados em Go para automatizar sua infraestrutura. Guia completo com exemplos práticos e padrões de produção.


# Go e Terraform: Infrastructure as Code com Go

O Terraform revolucionou a forma como gerenciamos infraestrutura, permitindo definir recursos como código. Embora o Terraform use sua própria linguagem de configuração (HCL), a linguagem Go é fundamental para estender suas capacidades através de **providers personalizados**.

Neste guia completo, você aprenderá a criar providers Terraform em Go, integrar Terraform com aplicações Go, e automatizar infraestrutura usando as melhores práticas de produção.

## Índice

1. [Por que Go e Terraform?](#por-que-go-e-terraform)
2. [Arquitetura de Providers Terraform](#arquitetura-de-providers-terraform)
3. [Criando seu Primeiro Provider](#criando-seu-primeiro-provider)
4. [Gerenciando Recursos com Go](#gerenciando-recursos-com-go)
5. [Testando Providers](#testando-providers)
6. [Integração com Aplicações Go](#integração-com-aplicações-go)
7. [Padrões de Produção](#padrões-de-produção)
8. [Deploy e Distribuição](#deploy-e-distribuição)

---

## Por que Go e Terraform?

### Vantagens da Combinação

**1. Performance Nativa**
Providers Terraform são executados como binários nativos, e Go oferece excelente performance com compilação estática.

**2. Ecossistema Rico**
O próprio Terraform é escrito em Go, o que significa que você pode estudar o código-fonte e aprender com os melhores.

**3. Bibliotecas Cloud Maturas**
Go possui SDKs oficiais para AWS, Azure, GCP, Kubernetes e praticamente qualquer serviço cloud.

**4. Binário Único**
Providers compilados em Go são binários auto-contidos, facilitando distribuição e execução.

```go
// Vantagem: provider como binário único
// terraform-provider-example v1.0.0
// ├── Não requer runtime
// ├── Cross-platform (Linux, macOS, Windows)
// └── Baixo consumo de memória
```

### Casos de Uso Comuns

- **Providers Customizados**: Integrar APIs internas ou serviços específicos
- **Módulos Reutilizáveis**: Criar abstrações para sua organização
- **Automação**: Executar Terraform a partir de aplicações Go
- **Validação**: Implementar políticas e validações customizadas

---

## Arquitetura de Providers Terraform

### Componentes Principais

Um provider Terraform consiste em três componentes principais:

```
┌─────────────────────────────────────┐
│         Terraform Core              │
│   (Escrito em Go - Open Source)     │
└─────────────┬───────────────────────┘
              │ gRPC
              ▼
┌─────────────────────────────────────┐
│      Terraform Provider             │
│   (Seu código em Go)                │
│                                     │
│  • Provider Configuration          │
│  • Data Sources                    │
│  • Resources                       │
└─────────────┬───────────────────────┘
              │ HTTP/HTTPS
              ▼
┌─────────────────────────────────────┐
│      API do Serviço                 │
│   (AWS, Azure, sua API, etc)       │
└─────────────────────────────────────┘
```

### SDK do Terraform

O Terraform Provider SDK v2 é a biblioteca oficial para criar providers:

```bash
go get github.com/hashicorp/terraform-plugin-sdk/v2
```

**Estrutura de um Provider:**

```go
package main

import (
    "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    "github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
)

func main() {
    plugin.Serve(&plugin.ServeOpts{
        ProviderFunc: func() *schema.Provider {
            return &schema.Provider{
                // Configuração do provider
                Schema: map[string]*schema.Schema{
                    "api_key": {
                        Type:        schema.TypeString,
                        Required:    true,
                        Sensitive:   true,
                        Description: "API key for authentication",
                    },
                },
                // Recursos gerenciados
                ResourcesMap: map[string]*schema.Resource{
                    "example_server": resourceServer(),
                },
                // Data sources
                DataSourcesMap: map[string]*schema.Resource{
                    "example_regions": dataSourceRegions(),
                },
                ConfigureContextFunc: providerConfigure,
            }
        },
    })
}
```

---

## Criando seu Primeiro Provider

### Estrutura do Projeto

```
terraform-provider-example/
├── main.go                    # Entry point
├── go.mod                     # Módulo Go
├── go.sum                     # Checksums
├── example/                   # Configurações de exemplo
│   └── main.tf
├── internal/
│   ├── provider/
│   │   ├── provider.go        # Configuração do provider
│   │   ├── resource_server.go # Resource exemplo
│   │   └── data_source.go     # Data source exemplo
│   └── client/
│       └── api.go             # Cliente HTTP da API
└── Makefile                   # Build automation
```

### Implementação Completa

**1. Configuração do Provider (`internal/provider/provider.go`)**

```go
package provider

import (
    "context"
    "os"

    "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
    "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    "terraform-provider-example/internal/client"
)

func New() *schema.Provider {
    return &schema.Provider{
        Schema: map[string]*schema.Schema{
            "host": {
                Type:        schema.TypeString,
                Optional:    true,
                DefaultFunc: schema.EnvDefaultFunc("EXAMPLE_HOST", "https://api.example.com"),
                Description: "URL base da API",
            },
            "api_key": {
                Type:        schema.TypeString,
                Required:    true,
                Sensitive:   true,
                DefaultFunc: schema.EnvDefaultFunc("EXAMPLE_API_KEY", nil),
                Description: "API Key para autenticação",
            },
            "timeout": {
                Type:        schema.TypeInt,
                Optional:    true,
                Default:     30,
                Description: "Timeout em segundos para requisições",
            },
        },
        ResourcesMap: map[string]*schema.Resource{
            "example_server": resourceServer(),
            "example_database": resourceDatabase(),
        },
        DataSourcesMap: map[string]*schema.Resource{
            "example_regions": dataSourceRegions(),
        },
        ConfigureContextFunc: configureProvider,
    }
}

func configureProvider(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
    var diags diag.Diagnostics

    host := d.Get("host").(string)
    apiKey := d.Get("api_key").(string)
    timeout := d.Get("timeout").(int)

    // Validação
    if apiKey == "" {
        diags = append(diags, diag.Diagnostic{
            Severity: diag.Error,
            Summary:  "API Key necessária",
            Detail:   "A API key deve ser fornecida via configuração ou variável de ambiente EXAMPLE_API_KEY",
        })
        return nil, diags
    }

    // Cria cliente da API
    config := client.Config{
        Host:    host,
        APIKey:  apiKey,
        Timeout: time.Duration(timeout) * time.Second,
    }

    c, err := client.New(config)
    if err != nil {
        diags = append(diags, diag.Diagnostic{
            Severity: diag.Error,
            Summary:  "Falha ao criar cliente",
            Detail:   err.Error(),
        })
        return nil, diags
    }

    return c, diags
}
```

**2. Cliente HTTP (`internal/client/api.go`)**

```go
package client

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "time"
)

type Config struct {
    Host    string
    APIKey  string
    Timeout time.Duration
}

type Client struct {
    httpClient *http.Client
    config     Config
}

type Server struct {
    ID       string            `json:"id"`
    Name     string            `json:"name"`
    Region   string            `json:"region"`
    Size     string            `json:"size"`
    Status   string            `json:"status"`
    Metadata map[string]string `json:"metadata"`
}

func New(config Config) (*Client, error) {
    if config.Host == "" {
        return nil, fmt.Errorf("host é obrigatório")
    }

    return &Client{
        httpClient: &http.Client{
            Timeout: config.Timeout,
        },
        config: config,
    }, nil
}

func (c *Client) CreateServer(ctx context.Context, server *Server) (*Server, error) {
    body, err := json.Marshal(server)
    if err != nil {
        return nil, err
    }

    req, err := http.NewRequestWithContext(ctx, "POST", 
        c.config.Host+"/servers", bytes.NewReader(body))
    if err != nil {
        return nil, err
    }

    req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
    req.Header.Set("Content-Type", "application/json")

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusCreated {
        return nil, fmt.Errorf("falha ao criar servidor: %s", resp.Status)
    }

    var result Server
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return nil, err
    }

    return &result, nil
}

func (c *Client) GetServer(ctx context.Context, id string) (*Server, error) {
    req, err := http.NewRequestWithContext(ctx, "GET",
        c.config.Host+"/servers/"+id, nil)
    if err != nil {
        return nil, err
    }

    req.Header.Set("Authorization", "Bearer "+c.config.APIKey)

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode == http.StatusNotFound {
        return nil, nil
    }

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("falha ao obter servidor: %s", resp.Status)
    }

    var server Server
    if err := json.NewDecoder(resp.Body).Decode(&server); err != nil {
        return nil, err
    }

    return &server, nil
}

func (c *Client) UpdateServer(ctx context.Context, server *Server) error {
    body, err := json.Marshal(server)
    if err != nil {
        return err
    }

    req, err := http.NewRequestWithContext(ctx, "PUT",
        c.config.Host+"/servers/"+server.ID, bytes.NewReader(body))
    if err != nil {
        return err
    }

    req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
    req.Header.Set("Content-Type", "application/json")

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("falha ao atualizar servidor: %s", resp.Status)
    }

    return nil
}

func (c *Client) DeleteServer(ctx context.Context, id string) error {
    req, err := http.NewRequestWithContext(ctx, "DELETE",
        c.config.Host+"/servers/"+id, nil)
    if err != nil {
        return err
    }

    req.Header.Set("Authorization", "Bearer "+c.config.APIKey)

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
        return fmt.Errorf("falha ao deletar servidor: %s", resp.Status)
    }

    return nil
}
```

**3. Resource (`internal/provider/resource_server.go`)**

```go
package provider

import (
    "context"
    "time"

    "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
    "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
    "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    "terraform-provider-example/internal/client"
)

func resourceServer() *schema.Resource {
    return &schema.Resource{
        Description: "Gerencia servidores na plataforma Example Cloud",

        CreateContext: resourceServerCreate,
        ReadContext:   resourceServerRead,
        UpdateContext: resourceServerUpdate,
        DeleteContext: resourceServerDelete,

        Importer: &schema.ResourceImporter{
            StateContext: schema.ImportStatePassthroughContext,
        },

        Timeouts: &schema.ResourceTimeout{
            Create: schema.DefaultTimeout(10 * time.Minute),
            Update: schema.DefaultTimeout(10 * time.Minute),
            Delete: schema.DefaultTimeout(10 * time.Minute),
        },

        Schema: map[string]*schema.Schema{
            "id": {
                Description: "ID único do servidor",
                Type:        schema.TypeString,
                Computed:    true,
            },
            "name": {
                Description: "Nome do servidor",
                Type:        schema.TypeString,
                Required:    true,
            },
            "region": {
                Description: "Região onde o servidor será criado",
                Type:        schema.TypeString,
                Required:    true,
                ForceNew:    true, // Mudança requer recriação
            },
            "size": {
                Description: "Tamanho do servidor (small, medium, large)",
                Type:        schema.TypeString,
                Required:    true,
                ValidateFunc: validation.StringInSlice([]string{
                    "small", "medium", "large",
                }, false),
            },
            "status": {
                Description: "Status atual do servidor",
                Type:        schema.TypeString,
                Computed:    true,
            },
            "metadata": {
                Description: "Metadados customizados",
                Type:        schema.TypeMap,
                Optional:    true,
                Elem:        &schema.Schema{Type: schema.TypeString},
            },
            "created_at": {
                Description: "Data de criação",
                Type:        schema.TypeString,
                Computed:    true,
            },
        },
    }
}

func resourceServerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
    c := meta.(*client.Client)

    server := &client.Server{
        Name:   d.Get("name").(string),
        Region: d.Get("region").(string),
        Size:   d.Get("size").(string),
    }

    if v, ok := d.GetOk("metadata"); ok {
        metadata := make(map[string]string)
        for k, v := range v.(map[string]interface{}) {
            metadata[k] = v.(string)
        }
        server.Metadata = metadata
    }

    created, err := c.CreateServer(ctx, server)
    if err != nil {
        return diag.FromErr(err)
    }

    d.SetId(created.ID)

    // Aguarda o servidor ficar pronto
    createStateConf := &resource.StateChangeConf{
        Pending: []string{"creating", "pending"},
        Target:  []string{"running", "active"},
        Refresh: func() (interface{}, string, error) {
            s, err := c.GetServer(ctx, created.ID)
            if err != nil {
                return nil, "", err
            }
            if s == nil {
                return nil, "", fmt.Errorf("servidor não encontrado após criação")
            }
            return s, s.Status, nil
        },
        Timeout:    d.Timeout(schema.TimeoutCreate),
        Delay:      10 * time.Second,
        MinTimeout: 5 * time.Second,
    }

    _, err = createStateConf.WaitForStateContext(ctx)
    if err != nil {
        return diag.FromErr(err)
    }

    return resourceServerRead(ctx, d, meta)
}

func resourceServerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
    c := meta.(*client.Client)
    var diags diag.Diagnostics

    server, err := c.GetServer(ctx, d.Id())
    if err != nil {
        return diag.FromErr(err)
    }

    if server == nil {
        d.SetId("")
        return diags
    }

    d.Set("name", server.Name)
    d.Set("region", server.Region)
    d.Set("size", server.Size)
    d.Set("status", server.Status)
    d.Set("metadata", server.Metadata)

    return diags
}

func resourceServerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
    c := meta.(*client.Client)

    server := &client.Server{
        ID:     d.Id(),
        Name:   d.Get("name").(string),
        Region: d.Get("region").(string),
        Size:   d.Get("size").(string),
    }

    if v, ok := d.GetOk("metadata"); ok {
        metadata := make(map[string]string)
        for k, v := range v.(map[string]interface{}) {
            metadata[k] = v.(string)
        }
        server.Metadata = metadata
    }

    if err := c.UpdateServer(ctx, server); err != nil {
        return diag.FromErr(err)
    }

    return resourceServerRead(ctx, d, meta)
}

func resourceServerDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
    c := meta.(*client.Client)
    var diags diag.Diagnostics

    if err := c.DeleteServer(ctx, d.Id()); err != nil {
        return diag.FromErr(err)
    }

    d.SetId("")

    return diags
}
```

---

## Gerenciando Recursos com Go

### Ciclo de Vida de Recursos

O Terraform segue um ciclo de vida bem definido:

```
terraform apply
       │
       ▼
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   Create()   │────▶│   Read()     │◀────│   Update()   │
│  (Criação)   │     │  (Leitura)   │     │ (Atualização)│
└──────────────┘     └──────────────┘     └──────────────┘
                                                   │
                                                   ▼
                                            ┌──────────────┐
                                            │   Delete()   │
                                            │  (Remoção)   │
                                            └──────────────┘
```

### Implementando Retry e Backoff

```go
// retry.go - Utilitários para retry com backoff exponencial
package provider

import (
    "context"
    "math"
    "time"
)

type RetryConfig struct {
    MaxRetries  int
    BaseDelay   time.Duration
    MaxDelay    time.Duration
    Retryable   func(error) bool
}

func retryWithBackoff(ctx context.Context, config RetryConfig, fn func() error) error {
    var err error
    
    for attempt := 0; attempt < config.MaxRetries; attempt++ {
        err = fn()
        if err == nil {
            return nil
        }
        
        if !config.Retryable(err) {
            return err
        }
        
        // Calcula delay com backoff exponencial
        delay := time.Duration(math.Pow(2, float64(attempt))) * config.BaseDelay
        if delay > config.MaxDelay {
            delay = config.MaxDelay
        }
        
        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-time.After(delay):
            continue
        }
    }
    
    return err
}

// Uso no resource
func resourceServerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
    c := meta.(*client.Client)
    
    err := retryWithBackoff(ctx, RetryConfig{
        MaxRetries: 3,
        BaseDelay:  1 * time.Second,
        MaxDelay:   30 * time.Second,
        Retryable: func(err error) bool {
            // Retry em erros temporários
            return isTemporaryError(err)
        },
    }, func() error {
        _, err := c.CreateServer(ctx, server)
        return err
    })
    
    if err != nil {
        return diag.FromErr(err)
    }
    
    // ... resto do código
}
```

---

## Testando Providers

### Testes de Aceitação

O SDK do Terraform fornece ferramentas para testes de aceitação:

```go
// provider_test.go
package provider

import (
    "os"
    "testing"

    "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
    "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

var testAccProvider *schema.Provider

func init() {
    testAccProvider = New()
}

func TestProvider(t *testing.T) {
    if err := New().InternalValidate(); err != nil {
        t.Fatalf("erro na validação interna: %s", err)
    }
}

func testAccPreCheck(t *testing.T) {
    if v := os.Getenv("EXAMPLE_API_KEY"); v == "" {
        t.Fatal("EXAMPLE_API_KEY deve ser definida para testes de aceitação")
    }
}

func TestAccResourceServer_basic(t *testing.T) {
    resource.Test(t, resource.TestCase{
        PreCheck:          func() { testAccPreCheck(t) },
        ProviderFactories: testAccProviderFactories,
        CheckDestroy:      testAccCheckServerDestroy,
        Steps: []resource.TestStep{
            {
                Config: testAccResourceServerConfig_basic(),
                Check: resource.ComposeTestCheckFunc(
                    testAccCheckServerExists("example_server.test"),
                    resource.TestCheckResourceAttr("example_server.test", "name", "test-server"),
                    resource.TestCheckResourceAttr("example_server.test", "region", "us-east-1"),
                    resource.TestCheckResourceAttr("example_server.test", "size", "small"),
                    resource.TestCheckResourceAttrSet("example_server.test", "id"),
                    resource.TestCheckResourceAttrSet("example_server.test", "status"),
                ),
            },
            {
                Config: testAccResourceServerConfig_update(),
                Check: resource.ComposeTestCheckFunc(
                    resource.TestCheckResourceAttr("example_server.test", "name", "test-server-updated"),
                    resource.TestCheckResourceAttr("example_server.test", "size", "medium"),
                ),
            },
        },
    })
}

func testAccCheckServerExists(n string) resource.TestCheckFunc {
    return func(s *terraform.State) error {
        rs, ok := s.RootModule().Resources[n]
        if !ok {
            return fmt.Errorf("não encontrado: %s", n)
        }

        if rs.Primary.ID == "" {
            return fmt.Errorf("ID não definido")
        }

        // Verifica se o servidor existe na API
        client := testAccProvider.Meta().(*client.Client)
        _, err := client.GetServer(context.Background(), rs.Primary.ID)
        if err != nil {
            return err
        }

        return nil
    }
}

func testAccCheckServerDestroy(s *terraform.State) error {
    client := testAccProvider.Meta().(*client.Client)

    for _, rs := range s.RootModule().Resources {
        if rs.Type != "example_server" {
            continue
        }

        server, err := client.GetServer(context.Background(), rs.Primary.ID)
        if err != nil {
            return err
        }

        if server != nil {
            return fmt.Errorf("servidor ainda existe: %s", rs.Primary.ID)
        }
    }

    return nil
}

func testAccResourceServerConfig_basic() string {
    return `
provider "example" {
    api_key = "test-api-key"
    host    = "http://localhost:8080"
}

resource "example_server" "test" {
    name   = "test-server"
    region = "us-east-1"
    size   = "small"
    
    metadata = {
        environment = "test"
        managed_by  = "terraform"
    }
}
`
}

func testAccResourceServerConfig_update() string {
    return `
provider "example" {
    api_key = "test-api-key"
    host    = "http://localhost:8080"
}

resource "example_server" "test" {
    name   = "test-server-updated"
    region = "us-east-1"
    size   = "medium"
}
`
}
```

### Executando Testes

```bash
# Testes unitários
go test ./...

# Testes de aceitação (requer servidor real ou mock)
TF_ACC=1 go test ./internal/provider/... -v -timeout 120m

# Testes específicos
TF_ACC=1 go test -run TestAccResourceServer_basic -v
```

---

## Integração com Aplicações Go

### Executando Terraform de Go

```go
// terraform_runner.go
package main

import (
    "context"
    "fmt"
    "os"
    "os/exec"
    "path/filepath"
)

type TerraformRunner struct {
    workingDir string
    envVars    map[string]string
}

func NewTerraformRunner(workingDir string) *TerraformRunner {
    return &TerraformRunner{
        workingDir: workingDir,
        envVars:    make(map[string]string),
    }
}

func (r *TerraformRunner) SetEnv(key, value string) {
    r.envVars[key] = value
}

func (r *TerraformRunner) Init(ctx context.Context) error {
    cmd := exec.CommandContext(ctx, "terraform", "init")
    cmd.Dir = r.workingDir
    cmd.Env = r.buildEnv()
    
    output, err := cmd.CombinedOutput()
    if err != nil {
        return fmt.Errorf("terraform init falhou: %w\n%s", err, output)
    }
    
    return nil
}

func (r *TerraformRunner) Plan(ctx context.Context) (string, error) {
    cmd := exec.CommandContext(ctx, "terraform", "plan", "-out=tfplan")
    cmd.Dir = r.workingDir
    cmd.Env = r.buildEnv()
    
    output, err := cmd.CombinedOutput()
    if err != nil {
        return "", fmt.Errorf("terraform plan falhou: %w\n%s", err, output)
    }
    
    return string(output), nil
}

func (r *TerraformRunner) Apply(ctx context.Context) error {
    cmd := exec.CommandContext(ctx, "terraform", "apply", "-auto-approve", "tfplan")
    cmd.Dir = r.workingDir
    cmd.Env = r.buildEnv()
    
    output, err := cmd.CombinedOutput()
    if err != nil {
        return fmt.Errorf("terraform apply falhou: %w\n%s", err, output)
    }
    
    fmt.Printf("Apply output:\n%s\n", output)
    return nil
}

func (r *TerraformRunner) Destroy(ctx context.Context) error {
    cmd := exec.CommandContext(ctx, "terraform", "destroy", "-auto-approve")
    cmd.Dir = r.workingDir
    cmd.Env = r.buildEnv()
    
    output, err := cmd.CombinedOutput()
    if err != nil {
        return fmt.Errorf("terraform destroy falhou: %w\n%s", err, output)
    }
    
    return nil
}

func (r *TerraformRunner) Output(ctx context.Context, name string) (string, error) {
    cmd := exec.CommandContext(ctx, "terraform", "output", "-raw", name)
    cmd.Dir = r.workingDir
    cmd.Env = r.buildEnv()
    
    output, err := cmd.Output()
    if err != nil {
        return "", err
    }
    
    return string(output), nil
}

func (r *TerraformRunner) buildEnv() []string {
    env := os.Environ()
    for k, v := range r.envVars {
        env = append(env, fmt.Sprintf("%s=%s", k, v))
    }
    return env
}

// Uso em aplicação
func deployInfrastructure(ctx context.Context, config Config) error {
    runner := NewTerraformRunner("./terraform")
    runner.SetEnv("TF_VAR_api_key", config.APIKey)
    runner.SetEnv("TF_VAR_region", config.Region)
    
    if err := runner.Init(ctx); err != nil {
        return err
    }
    
    plan, err := runner.Plan(ctx)
    if err != nil {
        return err
    }
    
    fmt.Println("Planned changes:", plan)
    
    if err := runner.Apply(ctx); err != nil {
        return err
    }
    
    serverID, err := runner.Output(ctx, "server_id")
    if err != nil {
        return err
    }
    
    fmt.Printf("Servidor criado: %s\n", serverID)
    return nil
}
```

---

## Padrões de Produção

### 1. Validação e Normalização

```go
func validateRegion(val interface{}, key string) (warns []string, errs []error) {
    validRegions := []string{
        "us-east-1", "us-west-2", "eu-west-1",
        "ap-southeast-1", "sa-east-1",
    }
    
    region := val.(string)
    for _, valid := range validRegions {
        if region == valid {
            return
        }
    }
    
    errs = append(errs, fmt.Errorf(
        "%q deve ser uma região válida: %v",
        key, validRegions,
    ))
    return
}

// Uso no schema
"region": {
    Type:         schema.TypeString,
    Required:     true,
    ValidateFunc: validateRegion,
}
```

### 2. Logging e Observabilidade

```go
import "github.com/hashicorp/terraform-plugin-log/tflog"

func resourceServerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
    tflog.Info(ctx, "Criando servidor", map[string]interface{}{
        "name":   d.Get("name"),
        "region": d.Get("region"),
    })
    
    // ... código de criação
    
    tflog.Debug(ctx, "Servidor criado com sucesso", map[string]interface{}{
        "id":     created.ID,
        "status": created.Status,
    })
    
    return resourceServerRead(ctx, d, meta)
}
```

### 3. Tratamento de Erros

```go
func handleAPIError(err error) diag.Diagnostics {
    var diags diag.Diagnostics
    
    switch e := err.(type) {
    case *client.AuthError:
        diags = append(diags, diag.Diagnostic{
            Severity: diag.Error,
            Summary:  "Erro de autenticação",
            Detail:   "Verifique sua API key",
        })
    case *client.RateLimitError:
        diags = append(diags, diag.Diagnostic{
            Severity: diag.Error,
            Summary:  "Rate limit excedido",
            Detail:   fmt.Sprintf("Aguarde %v antes de tentar novamente", e.RetryAfter),
        })
    case *client.NotFoundError:
        // Recurso não existe mais - limpa state
        return nil
    default:
        diags = append(diags, diag.FromErr(err)...)
    }
    
    return diags
}
```

---

## Deploy e Distribuição

### Build e Release

```makefile
# Makefile
VERSION ?= 1.0.0
BINARY := terraform-provider-example
OS_ARCH := $(shell go env GOOS)_$(shell go env GOARCH)

build:
	go build -o bin/$(BINARY)_v$(VERSION)

install: build
	mkdir -p ~/.terraform.d/plugins/registry.terraform.io/example/example/$(VERSION)/$(OS_ARCH)
	cp bin/$(BINARY)_v$(VERSION) ~/.terraform.d/plugins/registry.terraform.io/example/example/$(VERSION)/$(OS_ARCH)/$(BINARY)_v$(VERSION)

release:
	GOOS=darwin GOARCH=amd64 go build -o bin/$(BINARY)_v$(VERSION)_darwin_amd64
	GOOS=darwin GOARCH=arm64 go build -o bin/$(BINARY)_v$(VERSION)_darwin_arm64
	GOOS=linux GOARCH=amd64 go build -o bin/$(BINARY)_v$(VERSION)_linux_amd64
	GOOS=linux GOARCH=arm64 go build -o bin/$(BINARY)_v$(VERSION)_linux_arm64
	GOOS=windows GOARCH=amd64 go build -o bin/$(BINARY)_v$(VERSION)_windows_amd64.exe

test:
	go test ./... -v

testacc:
	TF_ACC=1 go test ./internal/provider/... -v -timeout 120m

.PHONY: build install release test testacc
```

### Registro no Terraform Registry

Para publicar seu provider no Terraform Registry:

1. Crie um repositório GitHub público
2. Use tags semânticas (`v1.0.0`)
3. Crie releases GitHub com binários
4. Siga o formato de naming: `terraform-provider-<nome>`
5. Documente no README

```hcl
# Uso por consumidores
terraform {
  required_providers {
    example = {
      source  = "example/example"
      version = "~> 1.0"
    }
  }
}

provider "example" {
  api_key = var.example_api_key
}

resource "example_server" "web" {
  name   = "web-server"
  region = "us-east-1"
  size   = "medium"
}
```

---

## Conclusão

Neste guia completo, você aprendeu:

✅ **Fundamentos**: Arquitetura de providers Terraform em Go
✅ **Implementação**: Criação completa de provider com CRUD
✅ **Cliente HTTP**: Padrões para comunicação com APIs
✅ **Testes**: Testes unitários e de aceitação
✅ **Integração**: Executando Terraform de aplicações Go
✅ **Produção**: Validação, logging, tratamento de erros
✅ **Distribuição**: Build multi-plataforma e publicação

### Próximos Passos

1. **[Go e Kubernetes](/tutoriais/go-kubernetes/)** - Orquestre containers com Go
2. **[Go Observability](/tutoriais/go-observability/)** - Logs, métricas e traces
3. **[Go Microservices](/tutoriais/go-microservices/)** - Arquitetura distribuída

### Recursos Adicionais

- [Terraform Plugin SDK Documentation](https://www.terraform.io/plugin/sdkv2)
- [Writing Custom Providers](https://www.terraform.io/plugin/writing-custom-providers)
- [Terraform Provider Scaffolding](https://github.com/hashicorp/terraform-provider-scaffolding)

---

## FAQ

**Q: Posso usar Terraform com qualquer API?**
R: Sim! Qualquer API REST (ou mesmo GraphQL/gRPC) pode ser integrada através de um provider customizado.

**Q: Qual a diferença entre SDK v1 e v2?**
R: O SDK v2 oferece melhor suporte a contextos, logging estruturado e melhor performance. Novos providers devem usar v2.

**Q: É necessário publicar no Registry?**
R: Não. Você pode distribuir binários diretamente ou usar registries privados.

**Q: Como testar sem API real?**
R: Use mocks, servidores de teste locais, ou implemente modos "dry-run" no seu cliente.
