Provider Terraform Cloudflare v4 → v5 — Runbook de Migração¶
Escopo: as quatro roots Terraform que pinam o provider
cloudflare/cloudflare. Issue #1679. A reescrita da config vai no repo; a migração de state abaixo deve ser executada por um operador com umCLOUDFLARE_API_TOKENreal — essas roots gerenciam infraestrutura VIVA de DNS / WAF / status-page na edge, então leia o runbook inteiro antes de rodar qualquer coisa.
| Root | Providers após a migração |
|---|---|
pkg/octopus/deploy/terraform/multi-region |
cloudflare ~> 5.21, aws ~> 6.53 |
platform/terraform/status-edge |
cloudflare ~> 5.21 |
platform/terraform/dns-caa |
cloudflare ~> 5.21 |
platform/terraform/dns-subzone-delegation |
cloudflare ~> 5.21, aws ~> 6.53 |
O bump do aws para ~> 6.53 em multi-region e dns-subzone-delegation
alinha essas duas roots com o resto do repo (PR #1681) — elas só estavam
travadas pela quebra do Cloudflare v5.
O que mudou por root¶
pkg/octopus/deploy/terraform/multi-region¶
cloudflare_record.connect_anycast/cloudflare_record.signal_anycastrenomeados paracloudflare_dns_record(rename de resource do v5). Blocosmovedestão nomain.tf, então o state acompanha automaticamente.- Ambos os resources
cloudflare_ruleset(octopus_waf_managed,octopus_waf_custom): os blocos repetidosrules { … }viraram um único list attributerules = [ { … } ];action_parametersvirou um objeto aninhado. Os valores dos atributos não mudaram — mesmas actions, mesmas expressions, mesmo id de managed-ruleset.
platform/terraform/status-edge¶
cloudflare_record.status_a→cloudflare_dns_record.status_a(+ blocomoved). Todos os atributos do record inalterados (A record proxied,ttl = 1, mesmo comment).cloudflare_ruleset.cache:rulesvirou list attribute;action_parameters,edge_ttlebrowser_ttlviraram objetos aninhados. Semântica inalterada (edge TTL override 15 s, browser TTL bypass).
platform/terraform/dns-caa¶
cloudflare_record.caa_issue["…"](×3) ecloudflare_record.caa_iodef→cloudflare_dns_record(+ blocosmovedcobrindo todas as instâncias dofor_each). O bloco CAAdata { … }virou um objetodata = { … }— mesmo payloadflags/tag/value.
platform/terraform/dns-subzone-delegation¶
cloudflare_zone.customer: argumento v4zone→ v5name; v4account_id→ v5account = { id = … }. Mesmo nome de resource type — o state faz upgrade in place, sem mudança de address.- O argumento v4
planfoi removido docloudflare_zoneupstream. O rate plan agora vive num NOVO resource companheirocloudflare_zone_subscription.customercom exatamente o mesmo mapeamento de tier (tierfree→free, todo o resto →pro). Para zonas que já existem, importe a subscription antes do primeiro apply (ver abaixo). cloudflare_zone_dnssec.customer: o v5 expõe a chave explicitamente —status = "active"preserva o comportamento v4 de habilitar na criação.cloudflare_api_token.customer: blocos v4policy { … }→ v5policies = [ { … } ]; ids de permission-group viraram objetos (permission_groups = [{ id = … }]) eresourcesagora é uma string JSON-encoded (jsonencode({ … })). Mesmo permission group id de DNS-Write, mesmo escopo por zona.- O output
customer_zones[*].zone_nameagora lêcloudflare_zone.customer[k].name(o v5 renomeou o atributozoneparaname); o formato do output não mudou.
Migração de state¶
Resources renomeados — automático via blocos moved¶
O provider v5 (≥ 5.19; pinamos ~> 5.21) implementa o provider-defined move
do Terraform (MoveResourceState) para
cloudflare_record → cloudflare_dns_record. Com Terraform ≥ 1.8 (o repo
usa 1.15.x), os blocos moved já commitados em cada root migram as entradas
de state in place no próximo plan/apply — sem cirurgia manual de state,
sem destroy/create. É o mesmo mecanismo que a ferramenta tf-migrate da
própria Cloudflare emite.
Resources com mesmo nome — automático via state upgraders¶
cloudflare_ruleset, cloudflare_zone, cloudflare_zone_dnssec e
cloudflare_api_token mantiveram seus type names; o provider v5 embute
state upgraders que convertem o formato de state v4 na primeira vez que o
provider v5 o lê. Nada a executar.
Resource novo — cloudflare_zone_subscription (só dns-subzone-delegation)¶
Para cada zona de customer que já existe no state, importe a subscription para o Terraform não emitir uma chamada redundante de set de plano:
cd platform/terraform/dns-subzone-delegation
export AWS_PROFILE=tlsstress-prod
export CLOUDFLARE_API_TOKEN=... # platform token
terraform init -upgrade
# one import per existing customer key; zone_id from the customer_zones output
terraform import 'cloudflare_zone_subscription.customer["<customer_key>"]' '<zone_id>'
Fallback — cirurgia manual de state (só se um bloco moved for rejeitado)¶
Se alguma ferramenta do pipeline não honrar o provider-defined move (ex.: um CLI que não é o Terraform), o caminho manual equivalente é remove + import. Os ids dos records já estão documentados no README de cada root:
# status-edge
terraform state rm cloudflare_record.status_a
terraform import cloudflare_dns_record.status_a "$ZONE/31de68523f9ce3eb44d39f7dfa812dd5"
# dns-caa
terraform state rm 'cloudflare_record.caa_issue["letsencrypt.org"]' \
'cloudflare_record.caa_issue["pki.goog"]' \
'cloudflare_record.caa_issue["amazon.com"]' \
cloudflare_record.caa_iodef
terraform import 'cloudflare_dns_record.caa_issue["letsencrypt.org"]' "$ZONE/d9260a33cacf58447acffe40ddd17da0"
terraform import 'cloudflare_dns_record.caa_issue["pki.goog"]' "$ZONE/ddc9f69aa37316c91004372bd921cacc"
terraform import 'cloudflare_dns_record.caa_issue["amazon.com"]' "$ZONE/9367272b025960f809ba33a74a980413"
terraform import cloudflare_dns_record.caa_iodef "$ZONE/0744823ec83f55ce38abf63e7c6395e4"
# multi-region (record ids: look up via the Cloudflare API / dashboard)
terraform state rm cloudflare_record.connect_anycast cloudflare_record.signal_anycast
terraform import cloudflare_dns_record.connect_anycast "$ZONE/<record_id>"
terraform import cloudflare_dns_record.signal_anycast "$ZONE/<record_id>"
Procedimento de execução (por root)¶
cd <root>
export CLOUDFLARE_API_TOKEN=... # scoped per root README
terraform state pull > pre-v5-$(date +%Y%m%d).tfstate.backup # rollback insurance
terraform init -upgrade
terraform plan -out=v5-migration.plan
Gate antes do apply — o plan DEVE mostrar:
- linhas
# cloudflare_record.X has moved to cloudflare_dns_record.Xpara cada record renomeado; 0 to add, 0 to change, 0 to destroy(multi-region/dns-subzone-delegation: adições são aceitáveis SOMENTE para imports decloudflare_zone_subscriptionque você pulou intencionalmente);- NENHUM
destroy, NENHUMreplace(-/+) em qualquercloudflare_dns_record,cloudflare_ruleset,cloudflare_zoneoucloudflare_api_token.
Checagem rápida:
terraform show -no-color v5-migration.plan | grep -E 'has moved to|must be replaced|will be destroyed|Plan:'
Se (e somente se) o gate passar:
terraform apply v5-migration.plan
terraform plan # second plan MUST be: No changes.
Rollback¶
- Antes do
apply: nada foi escrito —git revertno commit da migração,terraform init -upgradede volta para o lockfile v4, pronto. Blocosmovedsão só de plan-time. - Depois do
apply: o state agora usa addresses/formatos v5. Restaure o snapshot de state feito acima (terraform state push pre-v5-<date>.tfstate.backup) e façagit revertda config na mesma mudança; depois rodeterraform plancom v4 para confirmarNo changes. O backend S3 mantém objetos versionados como segunda fonte de recuperação. - Os records/rulesets em si nunca são tocados por uma migração correta — rollback é uma operação de config/state, não uma mudança de DNS.
Pós-migração¶
- Remover a regra de ignore do
cloudflareno.github/dependabot.ymlpara os minors v5.x voltarem a fluir (rastreado em #1679 — deliberadamente NÃO faz parte desta mudança; só acontece depois da migração de state viva ser executada). terraform planem cada root no próximo drift check agendado deve reportarNo changes.