# Generar la configuración

Un formulario que escribe el config.yaml de una ejecución normal y el paso de workflow de la Action, a partir de la lista de ajustes del propio binario.

Source: https://jmrplens.github.io/ghchronicle/es/configuration/builder/

Responde cómo es tu despliegue y esto escribe las tres cosas que lo ponen en
marcha: el `config.yaml` que lee una ejecución normal, el comando que lo
ejecuta y el paso que un workflow le da a la Action compuesta. Las tres salen
de un mismo conjunto de respuestas, y ninguna pide una credencial.

## Por qué no puede desviarse

Los controles no son una copia de los ajustes. `cmd/gen_config` exporta los
propios tipos de `internal/config` a un fichero que la página lee, incluido el
valor por omisión al que se resuelve cada clave, que mide validando una sonda en
vez de repetir un número. `make check-config-options` falla cuando ese fichero
deja de ser lo que produce el código, en la suite de análisis y en CI, así que
el formulario no puede ofrecer un ajuste que el binario no tiene ni perderse uno
que sí tiene.

La dirección contraria también se comprueba, y su límite conviene decirlo en voz
alta. Las configuraciones que este formulario escribe para los conjuntos de
respuestas de `internal/config/testdata/config-cases.json` pasan por el parser
real en las propias pruebas de `internal/config`, incluidas las formas que han
hecho tropezar a alguien: una cadencia por familia bajo `every.families`, un
`include_private` desactivado, un destino cuyas credenciales son referencias
`${VAR}`, una ejecución de solo tarjeta sin ningún destino y respuestas que se
espera que el parser rechace. Esos casos los genera el módulo que ejecuta esta
página, así que no pueden ser una copia suya, y cada rama de ese módulo que
puede cambiar lo que escribe tiene uno. Lo que el corpus no demuestra es el
comportamiento que ningún conjunto de respuestas produce, y por eso el fichero
que lo define enumera lo que no puede cubrir y por qué.

## Qué permite el formulario y rechaza el parser

Un formulario es un formulario: un destino es una casilla y las claves sin las
que no se resuelve no lo son, así que unos pocos clics bastan para escribir un
fichero que muere al arrancar. La página avisa de aquellas que su propia lista
generada de ajustes puede ver, encima de las salidas, y nombra el resto aquí.
Rechazar el resto en el formulario obligaría a guardar en esta página una
segunda copia de una regla del parser, que es justo la desviación que todo este
montaje existe para evitar.

- Un destino activado con una clave obligatoria vacía se rechaza con nombre y
  ejemplo: `sinks.loki: url is required, for example http://loki:3100`. De esta
  avisa el formulario.
- Un campo de credencial respondido con la credencial en vez de con el nombre de
  una variable de entorno se queda fuera del fichero. De esta avisa el
  formulario.
- Un fichero sin nada dentro se rechaza: `the file is empty`.
- No indicar ninguna cuenta se rechaza: `targets: set at least one of user, orgs
  or repos`.
- `heartbeat: 0` se rechaza: tiene que ser positivo, y dejar la clave fuera es
  la forma de que el tic del bucle se derive solo.
- Una cadencia que no es una duración se rechaza donde se usa:
  `every.families.repo: time: invalid duration "soon"`.
- `sinks.elasticsearch.api_key` rellenado a la vez que
  `sinks.elasticsearch.username` se rechaza: `sinks.elasticsearch: set either
  api_key or username and password, not both`.
- Escribir `-` en `sinks.sql.path` con `sinks.stdout` activado se acepta y deja
  dos escritores sobre el mismo flujo; elige uno.

> **Qué hace el formulario con una credencial**
>
> Donde un ajuste es una credencial, el formulario pide el NOMBRE de una
> variable de entorno y escribe `${NOMBRE}` en el fichero. Esa es la expansión
> que hace el binario al arrancar, y es lo que permite publicar el fichero. Una
> respuesta que no es un nombre de variable no se escribe: el formulario lo dice
> y deja la clave fuera, en vez de convertir un token pegado en una referencia a
> una variable que no va a existir nunca. Hay un campo que eso no cubre,
> `sinks.otlp.headers`, que es texto libre porque una cabecera no siempre es una
> credencial: lo que escribas ahí se escribe tal cual, así que pon una
> referencia `${VAR}`. El token de la Action es un secreto del repositorio, que
> es lo que referencia el paso.

## El formulario

Esta página es un formulario que escribe una configuración. Lo que sigue es el inventario que ofrece, generado a partir de los propios tipos del binario, y las tres salidas de las que parte.

- **La cuenta y su API**

  - `github`: un bloque de ajustes
  - `github.token`: string, obligatorio, una credencial, como `${GITHUB_TOKEN}`
  - `github.base_url`: string, como `https://github.example.com/api/v3`
  - `github.web_url`: string, como `https://github.example.com`
  - `github.timeout`: string, por omisión `30s`, como `30s`
  - `github.reserve_rate`: int, por omisión `500`, como `500`

- **Qué recoger**

  - `targets`: un bloque de ajustes
  - `targets.user`: string, como `your-github-login`
  - `targets.orgs`: list, como `some-org, another-org`
  - `targets.repos`: list, como `someone/one-repo`
  - `targets.exclude`: list, como `someone/experiment-*`
  - `targets.include_forks`: bool, por omisión `false`, como `false`
  - `targets.include_archived`: bool, por omisión `false`, como `false`
  - `targets.include_private`: bool, por omisión `true`, como `true`

- **Dónde van los puntos**

  - `sinks`: un bloque de ajustes
  - `sinks.influxdb`: un bloque de ajustes
  - `sinks.influxdb.url`: string, obligatorio, como `http://localhost:8181`
  - `sinks.influxdb.token`: string, una credencial, como `${INFLUX_TOKEN}`
  - `sinks.influxdb.org`: string, por omisión `default`, como `default`
  - `sinks.influxdb.bucket`: string, obligatorio, como `github`
  - `sinks.influxdb.batch`: int, como `5000`
  - `sinks.influxdb.exclude`: list, por omisión `gh_job_log`, como `gh_job_log`
  - `sinks.influxdb.dedupe`: bool, por omisión `true`, como `true`
  - `sinks.prometheus`: un bloque de ajustes
  - `sinks.prometheus.listen`: string, por omisión `:9605`, como `127.0.0.1:9605`
  - `sinks.prometheus.path`: string, por omisión `/metrics`, como `/metrics`
  - `sinks.prometheus.no_prime`: bool, por omisión `false`, como `false`
  - `sinks.otlp`: un bloque de ajustes
  - `sinks.otlp.endpoint`: string, obligatorio, como `http://collector:4318/v1/metrics`
  - `sinks.otlp.headers`: map, una credencial, como `Authorization: Bearer ${OTLP_TOKEN}`
  - `sinks.otlp.service`: string, como `ghchronicle`
  - `sinks.otlp.raw`: bool, por omisión `false`, como `false`
  - `sinks.otlp.batch`: int, como `2000`
  - `sinks.otlp.repeat`: string, como `1m`
  - `sinks.loki`: un bloque de ajustes
  - `sinks.loki.url`: string, obligatorio, como `http://loki:3100/loki/api/v1/push`
  - `sinks.loki.tenant_id`: string, como `tenant-one`
  - `sinks.loki.labels`: map, como `job: ghchronicle`
  - `sinks.loki.batch`: int, como `1000`
  - `sinks.loki.max_age`: string, como `1h`
  - `sinks.file`: un bloque de ajustes
  - `sinks.file.path`: string, obligatorio, como `/var/log/ghchronicle/points.lp`
  - `sinks.file.format`: string, uno de `influx`, `json`
  - `sinks.file.max_bytes`: int, como `67108864`
  - `sinks.file.keep`: int, como `5`
  - `sinks.stdout`: bool, por omisión `false`, como `false`
  - `sinks.stdout_format`: string, uno de `influx`, `json`
  - `sinks.telegraf`: un bloque de ajustes
  - `sinks.telegraf.url`: string, obligatorio, como `http://telegraf:8186/telegraf`
  - `sinks.telegraf.username`: string, como `telegraf`
  - `sinks.telegraf.password`: string, una credencial, como `${TELEGRAF_PASSWORD}`
  - `sinks.telegraf.batch`: int, como `5000`
  - `sinks.telegraf.dedupe`: bool, por omisión `true`, como `true`
  - `sinks.graphite`: un bloque de ajustes
  - `sinks.graphite.addr`: string, obligatorio, como `graphite:2003`
  - `sinks.graphite.prefix`: string, por omisión `github`, como `github`
  - `sinks.graphite.batch`: int, como `1000`
  - `sinks.graphite.dedupe`: bool, por omisión `true`, como `true`
  - `sinks.sql`: un bloque de ajustes
  - `sinks.sql.dialect`: string, por omisión `postgres`, uno de `postgres`
  - `sinks.sql.path`: string, obligatorio, como `/var/lib/ghchronicle/points.sql`
  - `sinks.sql.max_bytes`: int, como `67108864`
  - `sinks.sql.keep`: int, como `5`
  - `sinks.sql.dedupe`: bool, por omisión `true`, como `true`
  - `sinks.elasticsearch`: un bloque de ajustes
  - `sinks.elasticsearch.url`: string, obligatorio, como `http://elasticsearch:9200`
  - `sinks.elasticsearch.prefix`: string, por omisión `ghchronicle`, como `ghchronicle`
  - `sinks.elasticsearch.username`: string, como `elastic`
  - `sinks.elasticsearch.password`: string, una credencial, como `${ES_PASSWORD}`
  - `sinks.elasticsearch.api_key`: string, una credencial, como `${ES_API_KEY}`
  - `sinks.elasticsearch.batch`: int, como `1000`
  - `sinks.elasticsearch.dedupe`: bool, por omisión `true`, como `true`
  - `sinks.dedupe_file`: string, por omisión `ghchronicle-state-written.bin`, como `/var/lib/ghchronicle/state-written.bin`
  - `sinks.dedupe_horizon`: string, por omisión `720h`, como `720h`

- **Cada cuánto**

  - `every`: un bloque de ajustes
  - `every.default`: string, como `15m`
  - `every.groups`: map, como `ci: 1m`
  - `every.families`: map, como `deps: 24h`

- **El registro de la ejecución**

  - `log`: un bloque de ajustes
  - `log.level`: string, por omisión `info`, uno de `debug`, `info`, `warn`, `error`
  - `log.format`: string, por omisión `text`, uno de `text`, `json`
  - `log.file`: string, como `/var/log/ghchronicle/ghchronicle.log`
  - `log.max_bytes`: int, como `67108864`
  - `log.keep`: int, como `5`

- **La ejecución**

  - `heartbeat`: string, como `15s`
  - `groups`: list, como `audience, account, repos`
  - `state_file`: string, por omisión `ghchronicle-state.json`, como `/var/lib/ghchronicle/state.json`
  - `backfill`: un bloque de ajustes
  - `backfill.since`: string, como `2y`

- **config.yaml**

  Guárdalo junto al binario, o en la ruta que pases a -config, y ejecútalo con el comando de abajo.

  ```yaml
  github:
    token: ${GITHUB_TOKEN}
  targets:
    user: octocat
  sinks:
    stdout: true
  ```

- **El comando que lo ejecuta**

  ```sh
  ghchronicle -config config.yaml -once
  ```

- **Paso del workflow**

  Estas respuestas necesitan ajustes que ningún input transporta, así que el paso lee el fichero de arriba. Publícalo en esa ruta.

  ```yaml
  - uses: jmrplens/ghchronicle@v1
    with:
      token: ${{ secrets.GHCHRONICLE_TOKEN }}
      config: .github/ghchronicle.yaml
  ```

## Qué hacer con cada salida

No hay un único comando, y por eso el formulario lo escribe. Una configuración
que nombra un destino se ejecuta con `-once`. Una que no nombra ninguno la
rechaza `-once`, con el parser pidiendo un destino que no querías, así que se
ejecuta con `-card-only` y una ruta para la tarjeta. El comando de arriba cambia
con las respuestas, y el paso también.

El paso va en los `steps:` de un job de workflow. Las respuestas que ningún
input transporta hacen que el paso lea el fichero, y el fichero hay que
publicarlo en la ruta que nombra el paso; las respuestas que los cuatro inputs
sí transportan se escriben en el propio paso, y entonces no hace falta fichero
ninguno. Un paso sin fichero que no nombra destino es una ejecución de tarjeta,
`mode: card` con una ruta en `card:`, porque eso es lo único que la Action
convierte en `-card-only`.

Las dos salidas tienen valores por omisión opuestos en un ajuste, y el paso se
escribe de forma que diga cuál te toca. `include-private` está desactivado salvo
que lo pidas, porque una tarjeta que cuenta repositorios privados publica sus
nombres en un README público; `targets.include_private` está activado salvo que
digas lo contrario, porque el token ya llega a ellos. Por eso un paso sin
fichero siempre escribe el input en vez de dejarlo en un valor por omisión que
significa lo contrario que el fichero de al lado. Mira
[GitHub Actions](/ghchronicle/es/install/actions/) para el workflow que lo
rodea, y [el token](/ghchronicle/es/start/token/) para lo que el secreto tiene
que poder hacer.

## Lo demás

- [El fichero](/ghchronicle/es/configuration/): para qué sirve cada bloque y qué
  pasa cuando un valor está mal.
- [Objetivos](/ghchronicle/es/configuration/targets/): qué repositorios.
- [Cadencias](/ghchronicle/es/configuration/cadences/): cada familia, su grupo y
  su intervalo de fábrica.
- [Elegir almacén](/ghchronicle/es/sinks/): una página por destino.
