Attribute™ at re:Invent, Nov 30 to Dec 4.Book my merch
Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

FinOps as Code, Parte 2: padrões avançados e gestão do seu tenant com Terraform

Esta página também está disponível em English, Deutsch, Español, Français, Italiano e 日本語.

Hannes HayashiBy Hannes HayashiOct 12, 202618 min read

Na parte um colocamos orçamentos, alocações, alertas e relatórios sob controle de versão. De lá para cá, o DoiT Terraform provider lançou cinco releases minor em cerca de dois meses (da v1.6 à v1.10).

Tanto o número de organizações que usam o provider quanto o tráfego de API cresceram consideravelmente desde então. As alocações respondem pela maior parte desse tráfego, e toda organização que lê seus orçamentos pelo provider também o usa para escrevê-los — seja criando, atualizando ou excluindo orçamentos.

Por isso, este post segue em duas direções:

  1. Mais fundo nos recursos principais. São padrões que você descobre depois de alguns meses de FinOps as code: gerar alocações, orçamentos e alertas a partir de um único mapa de times, health checks em tempo de plan e relatórios que saem do console como PDFs.
  2. Mais amplo, cobrindo o próprio tenant. Configurações no nível do cliente, roles customizadas, onboarding de usuários e service accounts com tokens rotativos. No final, um relatório semanal de FinOps vai rodar no GitHub Actions sem a chave de API pessoal de ninguém.

O provider agora cobre 20 resources e 85 data sources. Cada trecho de código abaixo foi testado em um tenant real da DoiT antes de entrar neste post (aplicado, replanejado para detectar drift e destruído onde era seguro fazer isso), e a maioria também está nos exemplos da documentação do provider.

Atualizando a partir de uma release v1.x anterior

Se você configurou tudo depois do primeiro post, provavelmente está na v1.5 ou anterior. A maioria dos times atualiza rápido (a grande maioria das organizações ativas já está na v1.6 ou superior), e o upgrade em si costuma ser tranquilo. No entanto, alguns atributos obsoletos foram removidos pelo caminho, e estes são os que podem te pegar de surpresa:

Versão Mudança O que fazer
v1.7 max_results nos data sources de listagem é um número Remova as aspas: max_results = 50
v1.8 doit_budget.scope removido Use scopes: um filtro cuja lista values contém todos os IDs de alocação
v1.8 doit_alert.config.attributions removido Use config.scopes
v1.8 doit_report.config.metric removido Use config.metrics (até quatro)
v1.8 Componente inverse_selection de doit_allocation removido Use inverse
v1.8 Layouts de relatório csv_export / sheets_export removidos Escolha um layout de visualização, como table
v1.8 request_timeout precisa ser maior que 120s (padrão 150s) Aumente ou remova valores customizados
v1.8 Resources e data sources de Insights removidos A API de Insights upstream foi descontinuada
v1.10 Operadores de limit_by_change usam códigos curtos gt, gte, lt, lte, b, nb em vez de >, >=, …
v1.10 doit_user.job_function removido Use job_title

O guia de remoção de atributos obsoletos traz os detalhes de migração para cada remoção.

Desde a v1.8, você também ganha mais robustez de graça. Os relatórios rodam pela API de execução assíncrona, em que o provider envia a requisição, consulta o status e busca o resultado para você. Requisições limitadas por rate limit fazem backoff corretamente, e requisições lentas exibem o erro da própria API em vez de estourarem o timeout local primeiro.

Padrões avançados para os recursos principais de FinOps

Alocações, orçamentos, alertas e relatórios são os quatro recursos principais da parte um, e respondem pela maior parte do tráfego de API do provider.

Alocações em escala: um mapa, todos os times

Se a sua configuração tem uma alocação, um orçamento e um alerta para cada time, não copie e cole. Em vez disso, defina seus times uma única vez:

locals {
teams = {
payments = { budget = 12000, label = "payments" }
search = { budget = 8000, label = "search" }
platform = { budget = 20000, label = "platform" }
}
}
# One single allocation per team, with anomaly detection switched on
resource "doit_allocation" "team" {
for_each = local.teams
name = "Team: ${each.key}"
description = "All costs labeled owner=${each.value.label}"
anomaly_detection = true
rule = {
formula = "A"
components = [{
type = "label"
key = "owner"
mode = "is"
values = [each.value.label]
}]
}
}
# The same teams as one group allocation that selects the singles above
resource "doit_allocation" "by_team" {
name = "Cost by Team"
description = "Team allocations combined into one group"
unallocated_costs = "Unowned"
rules = [for team in doit_allocation.team : {
action = "select"
id = team.id
}]
}

anomaly_detection passou a ser gravável em alocações individuais na v1.7, então o monitoramento de anomalias vive na sua configuração em vez de ser um checkbox que alguém precisa lembrar de marcar. A alocação de grupo também seleciona as alocações dos times em vez de redefini-las.

Dica: sempre monte grupos com select. Você também pode definir regras de grupo inline com action = "create", e a API da DoiT cria exatamente as mesmas alocações individuais nos bastidores. Mas essas alocações não são resources próprios do Terraform, então regras inline não conseguem definir coisas como anomaly_detection ou folder_id. Alocações individuais separadas mais select te dão resources de verdade, gerenciados.

Como cada time agora tem uma alocação real com detecção de anomalias, você pode perguntar quais times estão gerando anomalias e obter uma explicação de IA para a mais cara:

# All anomalies (no max_results, so every page is fetched), sorted so the
# costliest one comes first
data "doit_anomalies" "by_cost" {
sort_by = "costOfAnomaly"
sort_order = "desc"
}
locals {
# An anomaly can belong to several allocations; count it under each one
anomaly_cost_by_allocation = {
for name, costs in {
for pair in flatten([
for a in data.doit_anomalies.by_cost.anomalies : [
for al in a.allocations : { name = al.name, cost = a.cost_of_anomaly }
]
]) : pair.name => pair.cost...
} : name => { anomalies = length(costs), total_cost = sum(costs) }
}
}
data "doit_anomaly_explanation" "costliest" {
count = length(data.doit_anomalies.by_cost.anomalies) > 0 ? 1 : 0
id = data.doit_anomalies.by_cost.anomalies[0].id
}
output "costliest_anomaly_explanation" {
value = one(data.doit_anomaly_explanation.costliest[*].explanation.text)
sensitive = true
}

O atributo allocations nas anomalias (v1.10) lista todas as alocações às quais uma anomalia pertence, e doit_anomaly_explanation (v1.8) retorna a explicação gerada junto com os fatos por trás dela. No nosso tenant de teste, a principal anomalia voltou como "provavelmente causada por um pico no volume de requisições do Amazon SNS Tier 1, especialmente nas regiões US West 2 e US West 1".

Orçamentos que acompanham as alocações

Com o mapa de times pronto, orçamentos são só mais um for_each. Orçamentos também podem notificar no Slack, e desde a v1.8 o provider valida a configuração do Slack antes do apply:

resource "doit_budget" "team" {
for_each = local.teams
name = "Team: ${each.key}"
type = "recurring"
time_interval = "month"
currency = "USD"
amount = each.value.budget
start_period = 1767225600000 # 2026-01-01T00:00:00Z in milliseconds
alerts = [{ percentage = 80 }, { percentage = 100 }]
scopes = [{
type = "allocation_rule"
id = "allocation_rule"
mode = "is"
values = [doit_allocation.team[each.key].id]
}]
collaborators = [{ email = data.doit_current_user.me.email, role = "owner" }]
recipients = [data.doit_current_user.me.email]
recipients_slack_channels = [{
id = "C0123456789"
customer_id = "your-customer-id"
workspace = "your-slack-workspace"
}]
}

Adicionar um time agora é uma mudança de uma linha em local.teams, e a alocação, o orçamento e (na próxima seção) o alerta vêm junto automaticamente.

Desde a v1.8, doit_budgets também expõe a classificação de risco calculada pela DoiT no lado do servidor: um risk_status por orçamento (onTrack, atRisk ou unknown), e dá para filtrar por ele. Combine isso com um check block do Terraform e cada terraform plan avisa quando o orçamento de um time está entrando em zona de risco:

check "team_budgets_on_track" {
data "doit_budgets" "team_at_risk" {
name_contains = "Team: "
filter = "riskStatus:atRisk"
}
assert {
condition = data.doit_budgets.team_at_risk.row_count == 0
error_message = "Budgets at risk: ${join(", ", [for b in data.doit_budgets.team_at_risk.budgets : b.budget_name])}"
}
}

Check blocks geram warnings, não erros, então nunca bloqueiam um apply — mas quem revisa o plan vê o problema. Para orçamentos sugeridos por IA, o data source doit_budget_suggestions (v1.7) lista as recomendações pendentes com o nível de confiança e o valor sugerido, para que um humano possa revisá-las em um pull request.

Alertas que valem a pena ativar

Nos nossos dados de uso, alertas são o menos usado dos quatro recursos principais, e bem menos organizações gerenciam alertas pelo Terraform do que orçamentos. É uma lacuna que vale a pena fechar, porque os alertas receberam duas das adições mais práticas desde julho.

O problema clássico dos alertas de variação percentual é o ruído. Um serviço que custa US$ 2 por dia pula para US$ 10 e você recebe um alerta de "+400%" por oito dólares. ignore_values_range (v1.9) pula qualquer avaliação em que o valor da métrica esteja dentro dos limites, e recipients_slack_channels envia o alerta para onde o seu time realmente olha:

resource "doit_alert" "team_spike" {
for_each = local.teams
name = "Team ${each.key}: daily cost spike"
config = {
metric = { type = "basic", value = "cost" }
time_interval = "day"
condition = "percentage-change"
operator = "gt"
value = 30 # percent
currency = "USD"
ignore_values_range = {
lower_bound = 0
upper_bound = 50 # USD per day
}
# Evaluate each service separately, so one service spiking is enough
evaluate_for_each = "fixed:service_description"
scopes = [{
type = "label"
id = "owner"
mode = "is"
values = [each.value.label]
}]
}
recipients = [data.doit_current_user.me.email]
recipients_slack_channels = [{
id = "C0123456789"
shared = true
}]
}

Note que os limites são em unidades da métrica (aqui, USD por dia), não em porcentagem, e só se aplicam a condition = "percentage-change". Um canal compartilhado do Slack usa o ID e shared = true; um canal em um workspace conectado usa o ID e workspace = "<name>".

Relatórios: Top Movers, previsões e novos visuais

Duas adições de relatórios da v1.6 se destacam.

Top Movers. limit_by_change filtra linhas pela variação entre períodos, e limit_aggregation controla como as linhas excluídas são exibidas. Este relatório mantém apenas serviços cujo custo total está acima de US$ 1.000 e que cresceram 50% ou mais:

resource "doit_report" "top_movers" {
name = "Top Movers"
description = "Services with significant month-over-month growth"
config = {
metrics = [{ type = "basic", value = "cost" }]
aggregation = "total"
data_source = "billing"
time_interval = "month"
dimensions = [{ id = "service_description", type = "fixed" }]
time_range = {
mode = "last"
amount = 3
include_current = true
unit = "month"
}
metric_filter = {
metric = { type = "basic", value = "cost" }
operator = "gt"
values = [1000]
operand = "series_total"
}
limit_by_change = {
metric = { type = "basic", value = "cost" }
change_type = "percentage"
operator = "gte"
values = [50]
include_incomplete_data = false
}
limit_aggregation = "top"
display_values = "actuals_only"
layout = "table"
currency = "USD"
}
}

Previsões e temas. forecast_settings projeta gastos futuros a partir de dados históricos, e os layouts da v1.8 (column_and_line_chart, sankey_chart, trend_board e cumulative_comparison) oferecem mais formas de exibi-los. Adicione display_settings.theme_id e o relatório usa as cores da sua marca:

resource "doit_custom_theme" "brand" {
name = "Brand"
primary_color = "#1A73E8"
colors = {
light = ["#1A73E8", "#34A853", "#FBBC04", "#EA4335", "#A142F4", "#24C1E0"]
dark = ["#8AB4F8", "#81C995", "#FDD663", "#F28B82", "#C58AF9", "#78D9EC"]
}
}
resource "doit_report" "forecast" {
name = "Spend Forecast"
description = "Last 12 months of spend with a 3-month forecast"
config = {
metrics = [{ type = "basic", value = "cost" }]
aggregation = "total"
time_interval = "month"
currency = "USD"
data_source = "billing"
time_range = {
mode = "last"
amount = 12
unit = "month"
include_current = true
}
dimensions = [
{ id = "year", type = "datetime" },
{ id = "month", type = "datetime" },
]
forecast_settings = {
mode = "totals"
historical_time_intervals = 12
future_time_intervals = 3
}
layout = "column_and_line_chart"
display_values = "actuals_only"
sort_dimensions = "a_to_z"
display_settings = {
theme_id = doit_custom_theme.brand.id
number_scale = "thousands"
}
}
}

Uma ressalva: os valores de previsão ainda não entram nos resultados nem nas exportações dos relatórios. O tema, por outro lado, é aplicado do início ao fim, como mostra a próxima seção.

Relatórios saem do console: exportação em PDF e PNG

Agora você consegue tirar um relatório da DoiT como arquivo, sem ninguém abrir o console. Desde a v1.10, doit_report_result e doit_report_query aceitam file_output = "pdf" ou "png" e retornam uma URL de download assinada:

data "doit_report_result" "forecast_pdf" {
id = doit_report.forecast.id
file_output = "pdf"
lifecycle {
postcondition {
condition = self.file_output_url != null
error_message = "PDF rendering failed. Try applying again."
}
}
}
data "http" "forecast_pdf" {
url = data.doit_report_result.forecast_pdf.file_output_url
}
resource "local_sensitive_file" "forecast_pdf" {
filename = "${path.module}/spend-forecast.pdf"
content_base64 = data.http.forecast_pdf.response_body_base64
}

O PDF é um documento caprichado, com várias páginas e o nome da sua organização no cabeçalho, e o seu tema customizado vem junto: como o relatório de previsão acima define display_settings.theme_id, o PDF também sai nas cores da sua marca.

Troque local_sensitive_file por um aws_s3_object (que aceita o mesmo content_base64) e o PDF vai parar em um bucket. As execuções de relatório passam pela API de execução assíncrona da DoiT, então relatórios pesados podem demorar: aumente o timeout de leitura do data source com timeouts = { read = "30m" } se precisar.

Você nem precisa de um relatório salvo. doit_report_query renderiza uma consulta ad-hoc igualmente bem, o que é ótimo para um gráfico em um README, um comentário de pull request ou uma página de wiki:

data "doit_report_query" "top_services_png" {
file_output = "png"
config = {
metrics = [{ type = "basic", value = "cost" }]
aggregation = "total"
time_interval = "month"
currency = "USD"
time_range = {
mode = "last", amount = 3, unit = "month", include_current = false
}
dimensions = [
{ id = "year", type = "datetime" },
{ id = "month", type = "datetime" },
]
# Top 5 services by cost
group = [{
id = "service_description"
type = "fixed"
limit = { value = 5, sort = "desc", metric = { type = "basic", value = "cost" } }
}]
layout = "stacked_column_chart"
sort_dimensions = "a_to_z"
}
}

Uma consulta ad-hoc renderizada como gráfico PNG

Dica: defina sort_dimensions = "a_to_z" nas exportações. Sem isso, o gráfico renderizado ordena o eixo de tempo por valor, e setembro pode acabar aparecendo antes de agosto. Com isso, os meses ficam em ordem de calendário.

A URL de download é sensível e válida por até sete dias. Cada plan renderiza um arquivo novo, que é exatamente o que você quer para um relatório recorrente. Vamos colocar isso em prática no grande final.

Seu tenant como código

Até aqui, falamos só de dados de custo. Mas um dos recursos mais adotados do provider é o onboarding do AWS CloudConnect, com o resource e o módulo do post anterior. Os times claramente querem o setup da DoiT em código, não só os relatórios, e as três últimas releases cobrem o resto do tenant.

Configurações no nível do cliente

doit_customer (v1.8) gerencia as configurações no nível da organização. Você não cria um customer, você adota o que já tem — então comece com um import:

import {
to = doit_customer.main
id = "your-customer-id"
}
resource "doit_customer" "main" {
url_slug = "my-company"
settings = {
currency = "USD"
allowed_invite_domains = ["mycompany.com"]
mfa_required = true
}
contact = {
emails = ["finops@mycompany.com"]
}
}

Seu primeiro plan mostra exatamente como as configurações de hoje diferem do que você escreveu, o que já é uma auditoria útil por si só. Depois disso, desligar o MFA ou liberar um novo domínio de convite vira um pull request que alguém precisa aprovar, não um botão que qualquer um pode acionar.

Roles customizadas e usuários

doit_role (v1.10) gerencia roles customizadas. Os IDs de permissão são opacos, então não os digite na mão: derive-os das roles predefinidas. Esta role combina dois presets em um:

data "doit_roles" "all" {}
locals {
preset_permissions = {
for r in data.doit_roles.all.roles : r.name => r.permissions if r.type == "preset"
}
}
resource "doit_role" "finops_analyst" {
name = "FinOps Analyst"
description = "View Only plus everything a Finance User can see"
permissions = sort(distinct(concat(
local.preset_permissions["View Only"],
local.preset_permissions["Finance User"],
)))
}

O onboarding vira então um mapa, igual ao dos times lá atrás:

locals {
analysts = {
"ana@mycompany.com" = { first_name = "Ana", last_name = "Lopez" }
"ravi@mycompany.com" = { first_name = "Ravi", last_name = "Patel" }
}
}
resource "doit_user" "analyst" {
for_each = local.analysts
email = each.key
first_name = each.value.first_name
last_name = each.value.last_name
job_title = "Finance / Accounting"
role_id = doit_role.finops_analyst.id
}

Configurações assim precisam de uma identidade com direitos suficientes para gerenciar usuários. Desde a v1.9, doit_current_user expõe as permissions efetivas de quem está chamando, então você pode falhar cedo com uma mensagem legível em vez de um 403 no meio do apply:

data "doit_current_user" "me" {
lifecycle {
postcondition {
condition = alltrue([for p in ["UsersManager", "ServiceAccountManager"] : contains(self.permissions, p)])
error_message = "The identity running Terraform needs the UsersManager and ServiceAccountManager permissions."
}
}
}

Service accounts e tokens rotativos

Service accounts são o grande destaque da v1.10. Pipelines que falam com a API da DoiT muitas vezes rodam com a chave de API pessoal de alguém, com todas as permissões dessa pessoa. Uma service account dá à automação uma identidade própria, apenas com as permissões necessárias, e doit_service_account e doit_service_account_token gerenciam tanto a conta quanto seus tokens no Terraform.

Derive o nome e a expiração do token de um resource time_rotating e o Terraform emite um token novo sempre que a data de rotação tiver passado:

resource "doit_service_account" "ci" {
name = "ci-pipeline"
description = "Runs Terraform from CI"
permissions = ["cloudAnalyticsUser", "budgetsReadOnly"]
}
resource "time_rotating" "ci_token" {
rotation_days = 90
}
resource "doit_service_account_token" "ci" {
service_account_id = doit_service_account.ci.id
name = "ci-${formatdate("YYYYMMDD-hhmmss", time_rotating.ci_token.rfc3339)}"
expires_time = timeadd(time_rotating.ci_token.rotation_rfc3339, "168h")
lifecycle {
create_before_destroy = true
}
}

Quatro detalhes fazem isso funcionar:

  • Mudar name ou expires_time substitui o token, então é o timestamp de rotação no nome que dispara a emissão de um token novo. Isso também mantém os nomes únicos — o que é obrigatório enquanto o token antigo e o novo coexistem por um breve período.
  • create_before_destroy garante que o token novo existe antes de o antigo ser excluído. Forçamos uma rotação durante os testes: o Terraform criou o token novo, excluiu o antigo e o plan seguinte veio limpo.
  • A expiração é uma semana depois da data de rotação, então o token continua funcionando mesmo se ninguém rodar o próximo apply na hora certa.
  • access_token é retornado uma única vez, na criação. Ele é marcado como sensível. Entregue-o para onde for necessário dentro do mesmo apply.

O token é um token de API normal. Defina-o como DOIT_API_TOKEN e o provider (ou qualquer outro cliente da API da DoiT) se autentica como a service account e enxerga apenas as permissões que você concedeu.

Dica: service accounts não podem ter mais poder do que quem as criou. Solicitar permissões que o chamador não possui falha com 403. Repare também na nomenclatura: permissões de service account usam camelCase (cloudAnalyticsUser), enquanto doit_current_user.permissions as reporta em PascalCase (CloudAnalyticsUser).

Cloud bill shouldn't be a mystery

One platform for AI and Cloud optimization.

Notas rápidas

Mais algumas adições que valem a pena conhecer:

  • doit_service_quotas mostra a utilização de quotas da AWS e do GCP. Combine com um check block e cada plan avisa antes de você esbarrar em um limite:
check "service_quota_headroom" {
data "doit_service_quotas" "hot" {
min_utilization_percent = 80
}
assert {
condition = length(data.doit_service_quotas.hot.items) == 0
error_message = "Quotas at or above 80%: ${join(", ", [for q in data.doit_service_quotas.hot.items : "${q.cloud_provider}/${q.service}/${q.quota} (${q.utilization_percent}%)"])}"
}
}

No nosso tenant de teste, isso disparou imediatamente para uma quota de VPC da AWS em 80%. Note que a leitura de quotas exige a permissão Manage Settings.

  • doit_billing_explainer detalha uma fatura por payer, incluindo créditos, descontos, economias e cobranças de marketplace.
  • doit_widget / doit_widgets expõem os dados dos widgets de dashboard do Cloud Analytics.
  • doit_cloudconnect_supported_features mostra quais recursos e permissões estão ativos em cada conta AWS conectada.
  • Os filtros name_contains em doit_alerts, doit_allocations, doit_budgets, doit_labels e doit_reports transformam "encontrar tudo que pertence ao time X" em uma linha só.
  • Os data sources do PerfectScale for Commitments (doit_ps4c_*) e o resource doit_support_request_tags completam as releases.

Juntando tudo: um relatório semanal de FinOps sem chave de API humana

O último exemplo combina uma service account, um token rotativo, um check block, uma exportação em PDF e a Ava, a assistente de IA da DoiT, em um pipeline do GitHub Actions. Ele roda toda segunda-feira e publica um resumo de FinOps com o relatório completo anexado, usando duas pequenas configurações Terraform:

  • Bootstrap, executada uma única vez por um admin: cria uma service account com permissões mínimas, um token rotativo e o relatório, e então entrega o token e o ID do relatório ao GitHub. É o snippet de service account de cima mais o relatório, com dois resources do provider do GitHub no final:
resource "github_actions_secret" "doit_api_token" {
repository = "doit-finops-demo"
secret_name = "DOIT_API_TOKEN"
plaintext_value = doit_service_account_token.reporting.access_token
}
resource "github_actions_variable" "report_id" {
repository = "doit-finops-demo"
variable_name = "DOIT_REPORT_ID"
value = doit_report.weekly.id
}

Reexecutar o bootstrap depois da data de rotação emite um token novo e atualiza o secret no mesmo apply, então o pipeline nem percebe.

  • Reporting, executada pelo pipeline: um check block de orçamento, a exportação em PDF de antes e uma pergunta para a Ava. Não contém resources além do arquivo baixado, então não precisa de remote state:
data "doit_ava" "summary" {
question = "Summarize report ${var.report_id} in three short bullet points for an engineering audience. Mention the biggest week-over-week change."
}
output "summary" {
value = <<-EOT
## Weekly FinOps report
${data.doit_ava.summary.answer}
_The full report is attached to this run as `weekly-cost-report`._
EOT
}

O workflow em si é curto:

name: Weekly FinOps report
on:
schedule:
- cron: "0 7 * * 1" # Mondays 07:00 UTC
workflow_dispatch:
jobs:
report:
runs-on: ubuntu-latest
defaults:
run:
working-directory: reporting
env:
DOIT_API_TOKEN: ${{ secrets.DOIT_API_TOKEN }}
TF_VAR_report_id: ${{ vars.DOIT_REPORT_ID }}
steps:
- uses: actions/checkout@v7
- uses: hashicorp/setup-terraform@v4
with:
terraform_wrapper: false
- run: terraform init -input=false
- run: terraform apply -auto-approve -input=false
- run: terraform output -raw summary >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@v7
with:
name: weekly-cost-report
path: reporting/weekly-cost-report.pdf

Aqui está uma execução real. O check block sinalizou um orçamento em risco no log, o resumo da Ava está na página da execução e o PDF está anexado como artefato:

A página da execução do GitHub Actions com o resumo da Ava

Página 1 do PDF anexado à execução

Olhe o terceiro bullet do resumo da Ava e depois o gráfico: o salto do Cloud SQL na semana de 7 de setembro é exatamente o pico roxo na semana 37. O resumo da Ava é gerado por IA, então o texto muda de uma execução para outra, mas rende um primeiro parágrafo útil para uma manhã de segunda-feira.

O que aprendemos construindo isso:

  • Mantenha as permissões do robô no mínimo. A service account do pipeline tem só duas permissões: cloudAnalyticsUser e budgetsReadOnly. É também por isso que o check de quotas fica fora deste pipeline, já que exigiria Manage Settings.
  • O robô só enxerga orçamentos públicos. Service accounts não têm endereço de e-mail, então não dá para adicionar uma como colaboradora de orçamento. Orçamentos que o pipeline deve verificar precisam de public = "viewer" ou superior no doit_budget.
  • Ninguém copia token na mão. O token vai da API da DoiT direto para o secret do GitHub dentro de um único apply. A única outra cópia é o state do Terraform do bootstrap, então guarde esse state em um lugar seguro.

Para encerrar

Recapitulando:

  • Os recursos principais ganharam profundidade: gere alocações, orçamentos e alertas a partir de um único mapa, monte alocações de grupo com select e deixe os check blocks revisarem seus orçamentos a cada plan.
  • Relatórios saem do console: renderize relatórios salvos e consultas ad-hoc como PDFs e PNGs, com o seu próprio tema.
  • Seu tenant agora é código: configurações, roles, usuários e service accounts com tokens rotativos, para que seus pipelines não precisem mais emprestar a chave de API de uma pessoa.

Para onde ir a partir daqui:

  • Atualize para a v1.10 com a ajuda da tabela no início deste post.
  • Comece pelo mapa de times. Ele reúne alocações, orçamentos e alertas em uma única configuração.
  • Tire seus pipelines das chaves de API pessoais e migre para uma service account com token rotativo.
  • Acompanhe as releases do repositório (Watch → Custom → Releases). O provider suporta tudo o que a API da DoiT oferece, então novas funcionalidades da API aparecem lá conforme são adicionadas.

O provider é open source em github.com/doitintl/terraform-provider-doit, e a documentação traz a maioria dos exemplos deste post, e muitos outros. Se algo estiver faltando ou parecer estranho, abra uma issue e conte para a gente.