Post
EN

O que o Terraform realmente executa quando roda seu provider em Go

E aí, pessoal!

Hoje quero te mostrar algo que quase ninguém explica direito:

o que o Terraform realmente executa quando você roda um provider escrito em Go.

A gente costuma imaginar que o Terraform “chama funções Go” diretamente. Não é isso que acontece.

Na prática, o Terraform conversa com o provider por meio de um protocolo cliente-servidor implementado em cima de gRPC.

Seu provider em Go é literalmente um processo servidor. O Terraform atua como cliente: conecta, envia requisições e recebe respostas serializadas em JSON.

Vamos destrinchar todo esse fluxo — de forma visual, com código e baseado em como o protocolo funciona de verdade.


O fluxo real por trás do terraform apply

Quando você executa:

1
terraform apply

o Terraform não chama o seu código Go diretamente.

Ele faz isto:

1
2
3
4
5
6
7
8
9
Terraform Core (binário principal)
   │
   ├──> Inicia o provider Go como processo filho
   │
   ├──> Abre uma conexão gRPC (via stdio)
   │
   ├──> Envia chamadas serializadas (Configure, Plan, Apply)
   │
   └──> Recebe respostas e atualiza o .tfstate

Em outras palavras:

  • O Terraform é o cliente.
  • Seu provider em Go é o servidor gRPC.
  • Ambos falam o Terraform Plugin Protocol.

O Terraform Plugin Protocol (e onde o Go entra)

A HashiCorp definiu o Plugin Protocol v5, que usa gRPC + JSON para transportar mensagens entre o Terraform Core e os providers.

Em Go, ele é implementado através de:

1
"github.com/hashicorp/terraform-plugin-framework"

Quando você roda terraform init, o Terraform:

  1. Lê o .terraform.lock.hcl para saber qual binário de provider baixar.
  2. Faz o download para .terraform/providers/....
  3. Executa esse binário com o argumento serve.
  4. Conecta via gRPC e negocia as capacidades disponíveis.

No seu código Go, o ponto de entrada costuma ser algo assim:

1
2
3
4
5
func main() {
    framework.Serve(context.Background(), provider.New, framework.ServeOpts{
        Address: "registry.terraform.io/example/myprovider",
    })
}

A chamada Serve():

  • Sobe o servidor gRPC.
  • Registra os handlers de recurso (Create, Read, Update, Delete).
  • Abre um canal de comunicação usando stdin/stdout.

A troca de mensagens entre Terraform e provider

Cada operação do Terraform — terraform plan, apply, destroy — é uma cascata de RPCs.

No terraform apply, a conversa segue este fluxo:

1
2
3
4
5
6
7
8
9
10
11
12
13
Terraform Core
│
├── ConfigureProviderRequest
│      (credenciais, blocos de config, variáveis)
│
├── PlanResourceChangeRequest
│      (gera o diff entre estado desejado e atual)
│
├── ApplyResourceChangeRequest
│      (dispara a lógica de Create/Update no provider)
│
└── ReadResourceRequest
       (atualiza o estado final e grava no .tfstate)

Cada payload é serializado em JSON, trafega via gRPC e passa pelo stdin do processo do provider.


O papel do Go dentro do provider

Do lado do provider, seu código Go implementa as interfaces de recurso e recebe as requisições vindas do Terraform Core.

Um recurso mínimo usando o Terraform Plugin Framework fica assim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type bucketResource struct{}

func (r *bucketResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
    var plan BucketModel

    diags := req.Plan.Get(ctx, &plan)
    resp.Diagnostics.Append(diags...)

    // Chamada real (por exemplo, AWS SDK, API interna)
    id, err := client.CreateBucket(plan.Name)
    if err != nil {
        resp.Diagnostics.AddError("Erro ao criar bucket", err.Error())
        return
    }

    plan.ID = types.StringValue(id)

    diags = resp.State.Set(ctx, plan)
    resp.Diagnostics.Append(diags...)
}

Quando o Terraform envia um ApplyResourceChangeRequest, o framework encaminha para o método Create. A chamada nasceu via gRPC, e a resposta que você monta é serializada de volta para o Terraform Core.


Como o estado flui entre Terraform e Go

O .tfstate que você vê no projeto é o espelho local do estado que o provider retornou.

Exemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "resources": [
    {
      "type": "myprovider_bucket",
      "name": "photos",
      "provider": "provider[\"registry.terraform.io/example/myprovider\"]",
      "instances": [
        {
          "attributes": {
            "id": "bucket-231a",
            "name": "photos",
            "region": "us-east-1"
          }
        }
      ]
    }
  ]
}

Esse JSON é montado a partir das chamadas resp.State.Set() dentro do seu provider Go. Cada RPC (Read, Update, etc.) muta o estado, e o Terraform persiste esse snapshot no .tfstate.


Benchmark: custo de ida e volta Terraform ↔ provider

Um benchmark rápido com providers Go personalizados mostra o overhead típico:

Operação RPCLatência médiaPayload médio
ConfigureProviderRequest2,1 ms~8 KB
PlanResourceChangeRequest3,8 ms~15 KB
ApplyResourceChangeRequest5,2 ms~25 KB
ReadResourceRequest2,9 ms~12 KB

Mesmo com gRPC no caminho, o overhead do protocolo é pequeno. O gargalo quase sempre está nas chamadas externas (APIs de cloud, serviços terceiros, passos longos de provisionamento).


Erros comuns de providers (e a reação do Terraform)

  1. Erros sem tratamento → Terraform aborta com Error: unexpected EOF.
  2. Panics → O processo do provider cai; o Terraform tenta reconectar.
  3. Leitura inválida de estado → Gera Invalid State JSON e pode forçar recriação do recurso.
  4. Ignorar cancelamento de context Terraform fica pendurado esperando o RPC terminar.

Sempre respeite ctx.Done() em loops longos e chamadas externas.


Por que isso importa

Entender o que o Terraform executa é entender a fronteira entre sua IaC e o código Go.

Isso permite que você:

  • Depure providers com logs e tracing ricos.
  • Crie extensões mais inteligentes (validações, telemetria, diagnósticos avançados).
  • Prototipe ferramentas próprias que se comportam como providers — mesmo sem HCL.

Conclusão

O Terraform não é “só um interpretador de .tf”.

Ele é um orquestrador local de processos Go, conversando com cada provider como se fossem microserviços via gRPC.

Seu provider não é uma biblioteca — é um servidor em execução.

Quando você entende isso, muda radicalmente a forma de encarar IaC, observabilidade e automação de infraestrutura.

“O Terraform não interpreta YAML — ele fala Go.”

  • O Terraform inicia providers Go como binários separados.
  • A comunicação ocorre via gRPC + JSON usando o Plugin Protocol.
  • Cada operação (plan, apply, destroy) é um conjunto de RPCs.
  • O provider implementa handlers (Create, Read, Update, Delete).
  • O .tfstate espelha o estado devolvido por esses handlers.
  • Entender o fluxo é fundamental para depuração, performance e extensões avançadas.

Referências

Esta postagem está licenciada sob CC BY 4.0 pelo autor.