CDKTF em Go: escreva sua infraestrutura Terraform em Go puro
E aí, pessoal!
Se você já escreveu um provider customizado do Terraform em Go, sabe que a linguagem e o ecossistema combinam muito bem. Mas na hora de escrever a infraestrutura em si, você volta pro HCL.
O CDKTF muda isso. Com ele, você define seus recursos da AWS, GCP ou qualquer outro provider diretamente em Go. Loops, funções, structs, testes unitários na sua infra. O HCL vira um detalhe de implementação que você nunca precisa ver.
Como funciona
O CDKTF não substitui o Terraform. Ele fica em cima. Você escreve Go, roda cdktf synth, e ele gera o JSON que o Terraform entende. O terraform apply acontece normalmente por baixo.
1
Seu código Go → cdktf synth → terraform.json → terraform apply → infra criada
Você ganha a expressividade de uma linguagem de programação real sem abrir mão do ecossistema do Terraform: providers, state, planos de execução, tudo continua igual.
Instalando o CDKTF CLI
1
npm install -g cdktf-cli
Sim, o CLI é Node. Mas o código que você vai escrever é Go puro. O CLI é só scaffolding.
Verifique a instalação:
1
cdktf --version
Criando o projeto
1
2
mkdir minha-infra && cd minha-infra
cdktf init --template=go --local
A flag --local usa state local em vez do Terraform Cloud. Boa opção pra começar.
Estrutura gerada:
1
2
3
4
5
6
minha-infra/
main.go
go.mod
go.sum
cdktf.json
help/
O cdktf.json define os providers que você vai usar:
1
2
3
4
5
6
{
"language": "go",
"app": "go run main.go",
"terraformProviders": ["aws@~> 5.0"],
"terraformModules": []
}
Agora gera os bindings Go do provider AWS:
1
cdktf get
Isso baixa e gera o pacote Go com todos os recursos da AWS tipados. Demora alguns minutos na primeira vez.
Primeira stack: um bucket S3
O main.go gerado tem a estrutura base. Vamos criar um bucket S3:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// main.go
package main
import (
"github.com/aws/constructs-go/constructs/v10"
"github.com/aws/jsii-runtime-go"
"github.com/hashicorp/terraform-cdk-go/cdktf"
aws "github.com/cdktf/cdktf-provider-aws-go/aws/v19/provider"
s3 "github.com/cdktf/cdktf-provider-aws-go/aws/v19/s3bucket"
)
func NewMinhaStack(scope constructs.Construct, id string) cdktf.TerraformStack {
stack := cdktf.NewTerraformStack(scope, &id)
aws.NewAwsProvider(stack, jsii.String("AWS"), &aws.AwsProviderConfig{
Region: jsii.String("us-east-1"),
})
s3.NewS3Bucket(stack, jsii.String("meu-bucket"), &s3.S3BucketConfig{
Bucket: jsii.String("meu-bucket-huncoding"),
})
return stack
}
func main() {
app := cdktf.NewApp(nil)
NewMinhaStack(app, "minha-stack")
app.Synth()
}
Uma coisa que chama atenção: jsii.String("valor") em vez de só "valor". O CDKTF usa o runtime JSII por baixo, que exige ponteiros em todos os campos. É verboso, mas você se acostuma rápido.
Gera o JSON:
1
cdktf synth
Isso cria cdktf.out/stacks/minha-stack/cdk.tf.json com o Terraform JSON equivalente ao HCL que você teria escrito. Você nunca precisa mexer nesse arquivo.
Onde o Go começa a fazer diferença
Criar um bucket é simples em HCL também. O CDKTF começa a brilhar quando você tem lógica real.
Múltiplos ambientes com um loop
Em HCL, criar o mesmo recurso pra dev, staging e prod exige for_each ou módulos. Em Go:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func NewMinhaStack(scope constructs.Construct, id string) cdktf.TerraformStack {
stack := cdktf.NewTerraformStack(scope, &id)
aws.NewAwsProvider(stack, jsii.String("AWS"), &aws.AwsProviderConfig{
Region: jsii.String("us-east-1"),
})
envs := []string{"dev", "staging", "prod"}
for _, env := range envs {
env := env
s3.NewS3Bucket(stack, jsii.String("bucket-"+env), &s3.S3BucketConfig{
Bucket: jsii.String("huncoding-assets-" + env),
Tags: &map[string]*string{
"Environment": jsii.String(env),
"ManagedBy": jsii.String("cdktf"),
},
})
}
return stack
}
Três buckets criados, cada um com suas tags, num loop de três linhas.
Constructs reutilizáveis
Você pode extrair padrões em funções Go normais. Um bucket com versionamento e bloqueio de acesso público que todo projeto usa:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type AppBucketConfig struct {
Name string
Environment string
}
func NewAppBucket(stack cdktf.TerraformStack, config AppBucketConfig) s3.S3Bucket {
bucket := s3.NewS3Bucket(stack, jsii.String("bucket-"+config.Name), &s3.S3BucketConfig{
Bucket: jsii.String(config.Name),
Tags: &map[string]*string{
"Environment": jsii.String(config.Environment),
},
})
s3.NewS3BucketVersioningA(stack, jsii.String("versioning-"+config.Name), &s3.S3BucketVersioningAConfig{
Bucket: bucket.Bucket(),
VersioningConfiguration: &s3.S3BucketVersioningAVersioningConfiguration{
Status: jsii.String("Enabled"),
},
})
s3.NewS3BucketPublicAccessBlock(stack, jsii.String("public-access-"+config.Name), &s3.S3BucketPublicAccessBlockConfig{
Bucket: bucket.Id(),
BlockPublicAcls: jsii.Bool(true),
BlockPublicPolicy: jsii.Bool(true),
IgnorePublicAcls: jsii.Bool(true),
RestrictPublicBuckets: jsii.Bool(true),
})
return bucket
}
Agora em qualquer stack do projeto:
1
2
3
4
NewAppBucket(stack, AppBucketConfig{
Name: "huncoding-uploads",
Environment: "prod",
})
Um construct que cria bucket, versionamento e bloqueio de acesso público, sempre com as mesmas configurações de segurança, sem duplicação.
Múltiplas stacks
Projetos maiores organizam a infra em stacks separadas: rede, banco, aplicação. Em Go isso é só criar mais structs:
1
2
3
4
5
6
7
8
9
func main() {
app := cdktf.NewApp(nil)
NewRedeStack(app, "rede")
NewBancoStack(app, "banco")
NewAppStack(app, "aplicacao")
app.Synth()
}
Cada stack tem seu próprio state file. Você faz deploy de cada uma independentemente.
Fazendo o deploy
Com as credenciais AWS configuradas:
1
2
3
4
5
6
7
8
# Plano de execução
cdktf diff
# Deploy
cdktf deploy
# Destruir tudo
cdktf destroy
O cdktf diff é equivalente ao terraform plan. Mostra o que vai ser criado, modificado ou destruído antes de aplicar.
Testando a infra
Uma vantagem real do CDKTF em Go: você pode testar a geração do JSON com testes unitários normais, sem precisar de infraestrutura real:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// main_test.go
package main
import (
"encoding/json"
"os"
"testing"
"github.com/aws/constructs-go/constructs/v10"
"github.com/hashicorp/terraform-cdk-go/cdktf"
"github.com/stretchr/testify/assert"
)
func TestBucketsCriados(t *testing.T) {
app := cdktf.NewApp(nil)
stack := NewMinhaStack(app, "test-stack")
synth := cdktf.Testing().SynthScope(stack)
var config map[string]any
json.Unmarshal([]byte(synth), &config)
resources := config["resource"].(map[string]any)
buckets := resources["aws_s3_bucket"].(map[string]any)
assert.Contains(t, buckets, "bucket-dev")
assert.Contains(t, buckets, "bucket-staging")
assert.Contains(t, buckets, "bucket-prod")
}
Isso não cria nada na AWS. Só verifica que o JSON gerado tem os recursos esperados. Dá pra rodar no CI sem credenciais.
Vale a pena migrar?
Não precisa migrar nada. CDKTF funciona junto com HCL existente. Você pode começar usando pra novos módulos enquanto o restante continua em HCL.
Faz mais sentido pra times que já escrevem Go e querem consistência na stack, ou pra infra com muita lógica condicional e repetição que em HCL ficaria difícil de ler.
Se sua infra é simples e estável, HCL continua sendo a opção mais direta.
Conclusão
O CDKTF não é a solução pra tudo, mas preenche uma lacuna real: lógica de programação na definição de infraestrutura, sem abrir mão do Terraform por baixo. E em Go, com tipagem e reuso que uma linguagem real oferece, a diferença pra HCL fica clara rápido.
Se você já escreveu um provider customizado, o próximo passo natural é definir a infra que usa esse provider na mesma linguagem.
