> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bbrands.io/llms.txt
> Use this file to discover all available pages before exploring further.

# CI/CD para el monitoreo

> El workflow de Terraform de Better Stack: un carril de validación automático en los PRs y un carril de plan/apply manual y protegido por entorno — con las decisiones de seguridad detrás de cada salvaguarda.

<Info>
  Los cambios de monitoreo se despliegan a través del workflow de GitHub Actions
  **`Better Stack Terraform`** (`.github/workflows/betterstack-terraform.yml`). Tiene dos carriles
  claramente separados: un carril de **validación** estático que se ejecuta automáticamente en los pull
  requests, y un carril de **plan/apply** que solo se ejecuta cuando un humano lo lanza.
</Info>

## Dos carriles de un vistazo

```mermaid theme={null}
flowchart TD
    subgraph pr [Pull request]
        A["PR touches infra/betterstack/**"] --> V["validate job<br/>fmt · init -backend=false · validate · JSON check"]
        V --> G1{Green?}
        G1 -->|yes| M["Safe to merge"]
    end
    subgraph dispatch [Manual dispatch]
        D["workflow_dispatch<br/>environment + action"] --> GATE["gate job<br/>requires APPLY for apply"]
        GATE --> TF["terraform job<br/>in GitHub Environment"]
        TF --> OUT["Outputs summary (apply)"]
    end
```

## Carril de validación — automático, seguro por construcción

Se dispara en cada pull request a `master`, `release` o `develop` que toca
`infra/betterstack/**`, el script del runner, o el propio archivo del workflow. Se ejecuta
con **sin secretos y sin estado**, así que físicamente no puede cambiar Better Stack.

| Paso                             | Comando                               | Propósito                                 |
| -------------------------------- | ------------------------------------- | ----------------------------------------- |
| Verificación de formato          | `terraform fmt -check -recursive`     | Impone el formato canónico                |
| Init sin backend                 | `terraform init -backend=false`       | Resuelve solo módulos/providers           |
| Validate                         | `terraform validate`                  | Verifica los tipos de la configuración    |
| Verificación JSON del manifiesto | `JSON.parse` de cada `adopted/*.json` | Detecta archivos de adopción mal formados |

Este es el carril que ves ponerse verde en un PR de monitoreo. Prueba que la config está
bien formada; **no** se comunica con Better Stack.

## Carril de plan/apply — manual, protegido, por entorno

Este carril se ejecuta vía **`workflow_dispatch`** (pestaña Actions → Run
workflow) o como la etapa final del orquestador **Release Train**
(`.github/workflows/release-train.yml`). Toma dos entradas — el `environment`
objetivo (una lista fija: `development`, `certification`, `production`) y la
`action` (`plan` o `apply`) — más una cadena de confirmación.

Las ejecuciones manuales son **solo desde tags**: elige un tag CalVer de
release en "Use workflow from". La guarda compartida
(`.github/scripts/validate-release-tag.sh`) rechaza refs de branch y aplica la
matriz de canales (alpha → development, rc → development + certification,
estable → los tres), y exige que el tag tenga un GitHub Release.

```mermaid theme={null}
sequenceDiagram
    participant U as Operator
    participant GH as GitHub Actions
    participant Env as GitHub Environment
    participant BS as Better Stack

    U->>GH: Run workflow (env, action, confirm_apply)
    GH->>GH: gate — action=apply requires "APPLY"
    GH->>Env: terraform job (env secrets + protection rules)
    Env->>Env: require TF_BACKEND_OVERRIDE (else fail fast)
    Env->>Env: write backend_override.tf, terraform init
    Env->>BS: terraform plan / apply
    BS-->>Env: diff / applied ids
    Env->>GH: outputs summary (apply)
```

### La compuerta de confirmación

El job `gate` se ejecuta primero. Si `action = apply` y la entrada `confirm_apply` no
es exactamente `APPLY`, falla antes de que algo toque la infraestructura. `plan`
no necesita confirmación — es de solo lectura. Cuando el workflow es invocado
por el Release Train, la única confirmación `RELEASE` del train reemplaza el
`APPLY` tipeado (vía una entrada `confirmed` exclusiva de `workflow_call` que
los dispatch manuales no pueden establecer). La compuerta valida además el tag
de release con la guarda compartida.

### Aislamiento de entorno y secretos

El job `terraform` se ejecuta **dentro del GitHub Environment seleccionado**, de modo que las
reglas de protección de ese entorno (revisores requeridos, temporizadores de espera) y sus
secretos con alcance se aplican automáticamente.

<CardGroup cols={2}>
  <Card title="Secretos por entorno" icon="key">
    `BETTERSTACK_UPTIME_API_TOKEN`, `HEALTH_MONITOR_TOKEN`, y el opcional
    `LOGTAIL_API_TOKEN`. Definidos en cada uno de `development`, `certification` y
    `production`.
  </Card>

  <Card title="Secretos del repositorio (estado remoto)" icon="database">
    `TF_BACKEND_OVERRIDE` (el bloque `backend "s3"` completo), `TF_STATE_ACCESS_KEY`
    y `TF_STATE_SECRET_KEY`. Compartidos entre entornos; el estado permanece aislado
    por workspace.
  </Card>
</CardGroup>

### Fail-fast ante estado remoto ausente

Antes de `init`, el job verifica que `TF_BACKEND_OVERRIDE` exista. Si no lo hace,
falla de inmediato — CI nunca debe planear contra un estado local vacío,
lo que intentaría recrear cada recurso. Cuando está presente, el bloque se escribe
en `backend_override.tf` y `terraform init` toma el backend remoto.

### Bloqueo de concurrencia

Un grupo de concurrencia con clave por entorno
(`betterstack-terraform-<environment>`) con `cancel-in-progress: false`
garantiza que dos operaciones nunca se ejecuten contra el mismo entorno a la vez; una
segunda ejecución se encola detrás de la primera.

### Outputs

En `apply`, el paso final añade `terraform output` al resumen del job para que los
`status_page_resources_json` y `uptime_heartbeat_ids` resultantes sean visibles
sin volver a ejecutar nada.

## Cómo ejecutarlo

<Steps>
  <Step title="Abre el workflow">
    GitHub → **Actions** → **Better Stack Terraform** → **Run workflow**, y
    elige el **tag** de release en "Use workflow from" (los refs de branch son
    rechazados).
  </Step>

  <Step title="Ejecuta primero un plan">
    Elige el `environment` objetivo, establece `action = plan`, y ejecuta. Lee el diff
    en el log del job. Un plan `0 to add, 0 to change, 0 to destroy` significa que no hay drift.
  </Step>

  <Step title="Aplica cuando el plan sea lo que esperas">
    Ejecuta de nuevo con `action = apply` y escribe `APPLY` en `confirm_apply`. La compuerta
    pasa, el job se ejecuta dentro del entorno, y se publica el resumen de outputs.
  </Step>

  <Step title="Confirma la convergencia">
    Ejecuta un `plan` más — debería reportar que no hay cambios.
  </Step>
</Steps>

## Decisiones de seguridad

<AccordionGroup>
  <Accordion title="Por qué el apply nunca es automático">
    Los cambios de monitoreo afectan la página de estado pública y pueden paginar a los ingenieros
    de guardia. Un humano debe leer el plan y escribir explícitamente `APPLY`, para que un
    cambio de infraestructura sea siempre una acción deliberada y revisada — nunca un efecto
    secundario de hacer merge.
  </Accordion>

  <Accordion title="Por qué CI se niega a ejecutarse sin estado remoto">
    Terraform rastrea los ids de los recursos en su estado. Ejecutar contra un estado local
    vacío haría que Terraform crea que nada existe y recreara cada
    monitor, duplicando recursos en la página de estado. Requerir
    `TF_BACKEND_OVERRIDE` hace que ese fallo sea imposible.
  </Accordion>

  <Accordion title="Por qué un grupo de concurrencia por entorno">
    Dos applies solapados contra el mismo estado pueden corromperlo o generar una race en la
    API de Better Stack. El bloqueo por entorno serializa las operaciones mientras aún
    permite que distintos entornos se ejecuten en paralelo.
  </Accordion>

  <Accordion title="Por qué el carril de validación no tiene secretos">
    La validación de PR debería ser segura de ejecutar en cualquier cambio, incluyendo desde forks. Al
    usar `init -backend=false` y sin tokens, el carril puede verificar la corrección
    sin ninguna capacidad de alcanzar o mutar Better Stack.
  </Accordion>
</AccordionGroup>

## Código relacionado

* Workflow: `.github/workflows/betterstack-terraform.yml`
* Runner: `scripts/status-monitoring/terraform-apply.sh`
* Template del backend: `infra/betterstack/backend_override.tf.example`
