Communitygithub.com

dortort/skills

Habilidades reutilizáveis de agente para ferramentas de IA

O que é skills?

Este skill descreve um padrão de ponte (Bridge Pattern) para integrar o Terraform e o Wrangler na gestão de recursos do Cloudflare. O Terraform gerencia a infraestrutura (como namespaces KV, bancos D1, buckets R2), enquanto o Wrangler cuida da implantação de código e configuração de bindings. O skill aborda o problema central de que o Wrangler precisa de IDs de recursos criados pelo Terraform, fornecendo um script automatizado que extrai outputs do Terraform e os injeta na configuração do Wrangler. Ele suporta as plataformas Cloudflare, Terraform e Wrangler, e oferece uma abordagem DRY (Don't Repeat Yourself) para pipelines locais e de CI/CD. Além disso, discute alternativas, como bindings gerenciados pelo Terraform, para equipes que preferem mantê-lo como fonte única de verdade, e inclui orientações sobre segurança de tipos, adaptação a projetos existentes e ambientes de preview.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add dortort/skills

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Cloudflare IaC: Terraform + Wrangler

O Problema Central

As ferramentas do Cloudflare são divididas em dois mundos:

  • Terraform gerencia infraestrutura: zonas, registros DNS, políticas de acesso, regras de firewall, configurações de conta e a existência de recursos como namespaces KV, bancos D1, buckets R2 e Filas.
  • Wrangler gerencia a implantação de aplicações: código do Worker, projetos Pages, bindings para esses recursos e desenvolvimento local.

O desafio é que o Wrangler precisa de IDs de recursos que o Terraform cria. Um ID de namespace KV, um ID de banco D1, um nome de Fila — estes são outputs de terraform apply que devem fluir para wrangler.toml (ou wrangler.jsonc) antes que wrangler deploy funcione.

Este skill codifica um padrão DRY e automatizável para preencher essa lacuna.

Separação de Responsabilidades

O Terraform Possui

  • Zonas e registros DNS
  • Namespaces KV (cloudflare_workers_kv_namespace)
  • Bancos D1 (cloudflare_d1_database)
  • Buckets R2 (cloudflare_r2_bucket)
  • Filas (cloudflare_queue)
  • Políticas de acesso, regras WAF, page rules
  • Configurações de nível de conta
  • Qualquer recurso que deva existir antes da implantação do código da aplicação

O Wrangler Possui

  • Código fonte do Worker e empacotamento
  • Implantação de projetos Pages
  • Configuração de bindings (conectar Workers aos recursos criados pelo Terraform)
  • Desenvolvimento local (wrangler dev)
  • Segredos (via wrangler secret put)
  • Implantação (wrangler deploy)

A Zona Cinzenta

Alguns recursos podem ser gerenciados por qualquer ferramenta. O princípio norteador: se vários Workers ou projetos compartilham um recurso, o Terraform o possui. Se está no escopo de um único Worker, o Wrangler pode possuí-lo. Em caso de dúvida, prefira o Terraform — ele oferece rastreamento de estado, detecção de drift e uma única fonte de verdade para infraestrutura.

O recurso de provisionamento automático do Wrangler (que cria recursos KV/R2/D1 na primeira implantação) é conveniente para prototipagem, mas não deve ser usado em pipelines de produção. Ele cria recursos fora do estado do Terraform, levando a drift e recursos que não podem ser gerenciados ou desmontados sistematicamente.

O Padrão de Ponte

Visão Geral

terraform apply → terraform output -json → script de ponte → wrangler.jsonc → wrangler deploy

O script de ponte é um script Node.js no projeto, invocado via package.json. Ele lê os outputs do Terraform e os escreve no arquivo de configuração do Wrangler. O mesmo script é executado localmente e em CI/CD — sem duplicação.

Passo 1: Outputs do Terraform

Defina outputs para cada ID de recurso que o Wrangler precisa:

# outputs.tf

output "kv_namespace_id" {
  value = cloudflare_workers_kv_namespace.my_kv.id
}

output "d1_database_id" {
  value = cloudflare_d1_database.my_db.id
}

output "r2_bucket_name" {
  value = cloudflare_r2_bucket.my_bucket.name
}

output "account_id" {
  value = var.cloudflare_account_id
}

output "zone_id" {
  value = cloudflare_zone.my_zone.id
}

Passo 2: O Script de Ponte

Crie scripts/sync-wrangler-config.mjs — um script Node.js que lê os outputs do Terraform e gera (ou atualiza) a configuração do Wrangler:

#!/usr/bin/env node

// scripts/sync-wrangler-config.mjs
//
// Lê os outputs do Terraform e escreve wrangler.jsonc com os IDs de recursos corretos.
// Execute localmente ou em CI — mesmo comando, mesmo resultado.

import { execSync } from "node:child_process";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, "..");

// --- Configuração ---
// Ajuste esses caminhos para corresponder à estrutura do seu projeto.
const TERRAFORM_DIR = join(ROOT, "terraform");
const WRANGLER_CONFIG_PATH = join(ROOT, "wrangler.jsonc");
const TEMPLATE_PATH = join(ROOT, "wrangler.template.jsonc");

// --- Ler outputs do Terraform ---
function getTerraformOutputs() {
  try {
    const raw = execSync("terraform output -json", {
      cwd: TERRAFORM_DIR,
      encoding: "utf-8",
      stdio: ["pipe", "pipe", "pipe"],
    });
    const outputs = JSON.parse(raw);
    // terraform output -json envolve cada valor em { value, type, sensitive }
    const flat = {};
    for (const [key, entry] of Object.entries(outputs)) {
      flat[key] = entry.value;
    }
    return flat;
  } catch (err) {
    console.error("Falha ao ler outputs do Terraform. Você executou 'terraform apply'?");
    console.error(err.message);
    process.exit(1);
  }
}

// --- Gerar configuração do Wrangler ---
function generateConfig(outputs) {
  if (!existsSync(TEMPLATE_PATH)) {
    console.error(`Template não encontrado: ${TEMPLATE_PATH}`);
    console.error("Crie um wrangler.template.jsonc com tokens __PLACEHOLDER__.");
    process.exit(1);
  }

  let template = readFileSync(TEMPLATE_PATH, "utf-8");

  // Substituir tokens __PLACEHOLDER__ pelos valores dos outputs do Terraform.
  // Formato do token: __UPPER_SNAKE_CASE__ mapeando para nomes de outputs do terraform.
  for (const [key, value] of Object.entries(outputs)) {
    const token = `__${key.toUpperCase()}__`;
    template = template.replaceAll(token, String(value));
  }

  // Avisar sobre tokens não substituídos
  const remaining = template.match(/__[A-Z_]+__/g);
  if (remaining) {
    console.warn("Aviso: tokens não substituídos no template:", [...new Set(remaining)]);
  }

  writeFileSync(WRANGLER_CONFIG_PATH, template, "utf-8");
  console.log(`Escrito ${WRANGLER_CONFIG_PATH}`);
}

// --- Principal ---
const outputs = getTerraformOutputs();
generateConfig(outputs);

Passo 3: O Template

Crie wrangler.template.jsonc — uma configuração Wrangler com tokens de espaço reservado onde os outputs do Terraform devem ir:

{
  // Gerado a partir de wrangler.template.jsonc por scripts/sync-wrangler-config.mjs
  // Não edite diretamente — edite o template.
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-01-01",
  "account_id": "__ACCOUNT_ID__",

  "kv_namespaces": [
    {
      "binding": "MY_KV",
      "id": "__KV_NAMESPACE_ID__"
    }
  ],

  "d1_databases": [
    {
      "binding": "MY_DB",
      "database_id": "__D1_DATABASE_ID__",
      "database_name": "my-db"
    }
  ],

  "r2_buckets": [
    {
      "binding": "MY_BUCKET",
      "bucket_name": "__R2_BUCKET_NAME__"
    }
  ]
}

Passo 4: Scripts do package.json

{
  "scripts": {
    "sync": "node scripts/sync-wrangler-config.mjs",
    "predeploy": "npm run sync",
    "deploy": "wrangler deploy",
    "dev": "npm run sync && wrangler dev"
  }
}

npm run deploy executa automaticamente sync primeiro (via o hook predeploy). O mesmo comando npm run deploy funciona localmente e em CI/CD — DRY.

Passo 5: .gitignore

# Gerado pelo script de sincronização — não commitar
wrangler.jsonc

# Terraform
terraform/.terraform/
terraform/*.tfstate*

O wrangler.jsonc gerado não deve ser commitado. O template e o script de sincronização são a fonte da verdade.

Integração com CI/CD

O pipeline de CI/CD executa exatamente os mesmos comandos que um desenvolvedor executa localmente. A única diferença é como o estado e as credenciais do Terraform são fornecidos.

Estrutura Genérica do Pipeline

# Pseudocódigo — adapte para a sintaxe da sua plataforma de CI/CD

steps:
  # 1. Terraform aplica mudanças na infraestrutura
  - name: terraform
    run: |
      cd terraform
      terraform init
      terraform apply -auto-approve

  # 2. Script de ponte sincroniza outputs do Terraform na configuração do Wrangler
  # 3. Wrangler implanta a aplicação
  - name: deploy-worker
    run: npm run deploy
    env:
      CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

É isso. npm run deploy chama predeploy → sync → wrangler deploy. Igual ao local.

Credenciais

  • Terraform: precisa de um token de API do Cloudflare com permissão para gerenciar recursos de infraestrutura. Forneça via variável de ambiente CLOUDFLARE_API_TOKEN ou na configuração do provider.
  • Wrangler: precisa de um token de API do Cloudflare com permissão para implantar Workers. Frequentemente é o mesmo token, mas pode ser escopo separado para privilégio mínimo.
  • Estado do Terraform: use um backend remoto (S3, GCS, Terraform Cloud, etc.) para que CI e local compartilhem o mesmo estado.

Promoção de Ambiente

Para configurações multi-ambiente (staging, produção), use workspaces do Terraform ou arquivos de estado separados e ambientes do Wrangler:

// Em wrangler.template.jsonc
{
  "name": "my-worker",
  "env": {
    "staging": {
      "kv_namespaces": [{ "binding": "MY_KV", "id": "__STAGING_KV_NAMESPACE_ID__" }]
    },
    "production": {
      "kv_namespaces": [{ "binding": "MY_KV", "id": "__PRODUCTION_KV_NAMESPACE_ID__" }]
    }
  }
}

O script de sincronização lida com isso naturalmente — basta adicionar os outputs correspondentes do Terraform.

Alternativa: Bindings Gerenciados pelo Terraform

Para equipes que desejam o Terraform como fonte única de verdade para tudo — incluindo implantação — o recurso cloudflare_worker_version pode gerenciar bindings diretamente. Neste modelo, o Wrangler apenas empacota o código e o Terraform lida com o upload e a conexão dos bindings:

resource "cloudflare_worker_version" "my_worker" {
  account_id         = var.cloudflare_account_id
  worker_id          = cloudflare_worker.my_worker.id
  main_module        = "index.js"
  compatibility_date = "2025-01-01"

  bindings = [
    {
      type         = "kv_namespace"
      name         = "MY_KV"
      namespace_id = cloudflare_workers_kv_namespace.my_kv.id
    },
    {
      type = "d1"
      name = "DB"
      id   = cloudflare_d1_database.my_db.id
    },
    {
      type        = "r2_bucket"
      name        = "MY_BUCKET"
      bucket_name = cloudflare_r2_bucket.my_bucket.name
    }
  ]
}

Com esta abordagem:

  • wrangler deploy --dry-run --outdir dist empacota o código
  • O Terraform faz o upload do pacote e conecta todos os bindings via referências de recursos
  • Sem script de ponte, sem template — o Terraform resolve os IDs nativamente

Trade-off: Você perde a ergonomia de desenvolvimento local do Wrangler para configuração de bindings. Ainda precisará de um wrangler.toml para wrangler dev, o que significa alguma duplicação. O padrão de ponte (acima) é geralmente melhor quando os desenvolvedores usam ativamente wrangler dev, enquanto a abordagem apenas com Terraform é adequada para fluxos de trabalho centrados em CI onde o desenvolvimento local usa mocks ou uma configuração mínima.

Escolha o padrão de ponte quando a equipe executa wrangler dev regularmente. Escolha bindings gerenciados pelo Terraform quando o Terraform já é a ferramenta de implantação e o desenvolvimento local não precisa de bindings reais.

Segurança de Tipos: wrangler types --check

Após gerar ou atualizar a configuração do Wrangler, execute wrangler types para regenerar os bindings TypeScript. Em CI, use --check para falhar o build se os tipos estiverem desatualizados:

{
  "scripts": {
    "typecheck": "wrangler types --check",
    "predeploy": "npm run sync && npm run typecheck",
    "deploy": "wrangler deploy"
  }
}

Isso detecta incompatibilidades entre recursos gerenciados pelo Terraform e os tipos TypeScript do Worker antes da implantação.

Adaptando a um Projeto Existente

Ao aplicar este padrão a um projeto que já existe:

  1. Audite o que existe. Veja o wrangler.toml/wrangler.jsonc atual em busca de IDs de recursos codificados. Verifique se o Terraform já está em uso. Veja a configuração de CI/CD.

  2. Identifique os recursos. Liste cada ID de recurso na configuração do Wrangler. Determine quais já estão no Terraform e quais precisam ser importados.

  3. Importe recursos existentes para o Terraform usando terraform import em vez de recriá-los. Isso evita tempo de inatividade e perda de dados.

  4. Crie o template. Copie a configuração existente do Wrangler para wrangler.template.jsonc e substitua IDs codificados por tokens __PLACEHOLDER__.

  5. Escreva o script de ponte. Comece com o script de referência acima e ajuste caminhos e nomes de outputs.

  6. Configure o package.json. Adicione scripts sync, predeploy e deploy.

  7. Atualize o CI/CD. Substitua qualquer passo manual de passagem de IDs ou geração de configuração por npm run deploy.

  8. Adicione wrangler.jsonc ao .gitignore assim que o arquivo gerado estiver funcionando.

Padrões Comuns

Múltiplos Workers em um Monorepo

Quando um repositório contém vários Workers, cada um recebe seu próprio template e o script de sincronização lida com todos:

projeto/
├── terraform/
├── workers/
│   ├── api/
│   │   ├── src/
│   │   └── wrangler.template.jsonc
│   └── cron/
│       ├── src/
│       └── wrangler.template.jsonc
├── scripts/
│   └── sync-wrangler-config.mjs
└── package.json

O script de sincronização itera sobre os diretórios de workers e aplica a mesma lógica de substituição de tokens a cada template.

Segredos

Segredos não devem passar pelo script de ponte ou template. Use wrangler secret put (interativo) ou wrangler secret bulk (de um arquivo JSON, útil em CI). Segredos são gerenciados por Worker na API do Cloudflare, não em arquivos de configuração.

Em CI:

echo '{"API_KEY":"...","DB_PASSWORD":"..."}' | wrangler secret bulk

Ambientes de Preview

Para implantações de preview baseadas em PR, o script de sincronização pode aceitar um argumento de ambiente:

{
  "scripts": {
    "deploy:preview": "npm run sync -- --env preview && wrangler deploy --env preview"
  }
}

Solução de Problemas

SintomaCausa Provável
sync falha com "Falha ao ler outputs do Terraform"O Terraform não foi aplicado, ou você está no diretório errado. Execute terraform apply primeiro.
Tokens __TOKENS__ não substituídos na configuração geradaO nome do output do Terraform não corresponde ao token. Verifique se os nomes em terraform output correspondem ao padrão de token em maiúsculas.
Implantação do Wrangler falha com "recurso não encontrado"O ID do recurso do Terraform está desatualizado ou é do ambiente errado. Reexecute terraform apply e depois npm run sync.
Drift entre o estado do Terraform e o CloudflareUm recurso foi modificado fora do Terraform (possivelmente pelo provisionamento automático do Wrangler). Execute terraform plan para detectar e reconciliar.

Referência

  • scripts/sync-wrangler-config.mjs — O script de ponte (veja o diretório scripts/ para uma versão pronta para produção com análise de argumentos e suporte a múltiplos workers)
  • references/terraform-wrangler-mapping.md — Mapeia Terra

… [truncado para tradução]

Habilidades Relacionadas