feat: new client, store and docs
This commit is contained in:
@@ -14,15 +14,49 @@ Jenkins centralizado para el despliegue de software en terminales SCO (Self-Chec
|
|||||||
├── deploy/
|
├── deploy/
|
||||||
│ └── install.sh # Script desplegado en cada SCO
|
│ └── install.sh # Script desplegado en cada SCO
|
||||||
└── pipelines/
|
└── pipelines/
|
||||||
└── Jenkinsfile.ralph # Pipeline del cliente Ralph
|
├── Jenkinsfile.ralph # Pipeline del cliente Ralph
|
||||||
|
└── Jenkinsfile.lab # Pipeline del entorno de laboratorio
|
||||||
```
|
```
|
||||||
|
|
||||||
## Arquitectura
|
## Arquitectura
|
||||||
|
|
||||||
- **Jenkins master**: corre en Docker en un servidor (`172.19.13.204`).
|
- **Jenkins master**: corre en Docker en un servidor (`172.19.13.204`). No expone puertos al host; solo es alcanzable dentro de la red interna Docker `proxy`.
|
||||||
- **Agentes (SCOs)**: cada terminal SCO conecta al master vía JNLP (puerto 50000). El proceso del agente corre como `root`.
|
- **Nginx Proxy Manager (NPM)**: reverse proxy interno que resuelve el hostname `jenkins.laoficina1782.com` y hace forward hacia `jenkins-server:8080`. Corre en el mismo servidor, red `proxy`.
|
||||||
|
- **Cloudflare Tunnel (`cloudflared`)**: es el único punto de entrada desde internet. El contenedor `cloudflared` abre una conexión saliente hacia el edge de Cloudflare (autenticada con `TUNNEL_TOKEN`), por lo que **no hace falta abrir puertos entrantes en el firewall/router** del servidor. El tunnel está configurado del lado de Cloudflare para enrutar el hostname público hacia `http://npm:80` dentro de la red `proxy`.
|
||||||
|
- **Agentes (SCOs)**: cada terminal SCO conecta al master vía WebSocket (no JNLP), outbound hacia `wss://jenkins.laoficina1782.com/`. El proceso del agente corre como `root`.
|
||||||
- **Storage**: Minio almacena el software de cada cliente en buckets separados (ej: bucket `ralph`).
|
- **Storage**: Minio almacena el software de cada cliente en buckets separados (ej: bucket `ralph`).
|
||||||
|
|
||||||
|
### Flujo de tráfico
|
||||||
|
|
||||||
|
```
|
||||||
|
Internet
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Cloudflare Edge (DNS + proxy)
|
||||||
|
│ túnel saliente autenticado (TUNNEL_TOKEN)
|
||||||
|
▼
|
||||||
|
cloudflared (contenedor, red "proxy", sin puertos publicados)
|
||||||
|
│ http://npm:80
|
||||||
|
▼
|
||||||
|
NPM (nginx-proxy-manager) — vhost jenkins.laoficina1782.com
|
||||||
|
│ http://jenkins-server:8080
|
||||||
|
▼
|
||||||
|
jenkins-server (sin puertos publicados al host)
|
||||||
|
```
|
||||||
|
|
||||||
|
Jenkins **no** expone puertos al host: NPM y `cloudflared` se comunican con él por nombre de contenedor dentro de la red Docker `proxy`. Como no hay puertos entrantes abiertos en el servidor, toda la superficie pública depende de Cloudflare (WAF, DNS, TLS terminado en el edge).
|
||||||
|
|
||||||
|
> ⚠️ NPM publica además `80`, `443` y `81` directamente al host (ver `docker-compose.yml`). Si el firewall del servidor permite esas conexiones, Jenkins/NPM también serían alcanzables sin pasar por Cloudflare. Para que el tunnel sea realmente el único punto de entrada, esos puertos deberían quedar bloqueados a nivel de firewall/red salvo acceso local.
|
||||||
|
|
||||||
|
### Variables de entorno
|
||||||
|
|
||||||
|
El tunnel necesita el token generado en el dashboard de Cloudflare (Zero Trust → Networks → Tunnels):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cp .env.example .env
|
||||||
|
# editar .env y setear CLOUDFLARE_TUNNEL_TOKEN=<token>
|
||||||
|
```
|
||||||
|
|
||||||
### Labels de agentes
|
### Labels de agentes
|
||||||
|
|
||||||
Formato: `sco <cliente> <tienda>`
|
Formato: `sco <cliente> <tienda>`
|
||||||
@@ -39,7 +73,28 @@ Formato: `sco <cliente> <tienda>`
|
|||||||
docker compose up -d --build
|
docker compose up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
Jenkins queda disponible en `http://<server-ip>:8080` (admin / admin123).
|
Jenkins queda disponible en `https://jenkins.laoficina1782.com` una vez configurados el proxy y el tunnel.
|
||||||
|
|
||||||
|
### Nginx Proxy Manager
|
||||||
|
|
||||||
|
Panel de administración: `http://172.19.13.204:81` (o localmente en el servidor).
|
||||||
|
|
||||||
|
Configuración del proxy host para Jenkins:
|
||||||
|
|
||||||
|
| Campo | Valor |
|
||||||
|
|-------|-------|
|
||||||
|
| Domain Names | `jenkins.laoficina1782.com` |
|
||||||
|
| Scheme | `http` |
|
||||||
|
| Forward Hostname | `jenkins-server` |
|
||||||
|
| Forward Port | `8080` |
|
||||||
|
| Websockets Support | ✅ activado |
|
||||||
|
|
||||||
|
### Cloudflare Tunnel
|
||||||
|
|
||||||
|
1. En el dashboard de Cloudflare (Zero Trust → Networks → Tunnels), crear un tunnel y copiar su token.
|
||||||
|
2. Configurar la ruta pública (Public Hostname) del tunnel: `jenkins.laoficina1782.com` → `http://npm:80`.
|
||||||
|
3. Setear el token en `.env` como `CLOUDFLARE_TUNNEL_TOKEN` (ver arriba).
|
||||||
|
4. Levantar el stack: el contenedor `cloudflared` se conecta solo, sin necesidad de abrir puertos en el servidor.
|
||||||
|
|
||||||
## Conectar un agente SCO
|
## Conectar un agente SCO
|
||||||
|
|
||||||
@@ -61,7 +116,8 @@ Jenkins queda disponible en `http://<server-ip>:8080` (admin / admin123).
|
|||||||
[Service]
|
[Service]
|
||||||
User=root
|
User=root
|
||||||
ExecStart=/usr/bin/java -jar /opt/jenkins-agent/agent.jar \
|
ExecStart=/usr/bin/java -jar /opt/jenkins-agent/agent.jar \
|
||||||
-url http://<server-ip>:8080/ \
|
-url https://jenkins.laoficina1782.com/ \
|
||||||
|
-webSocket \
|
||||||
-secret <SECRET> \
|
-secret <SECRET> \
|
||||||
-name "<nombre-del-nodo>" \
|
-name "<nombre-del-nodo>" \
|
||||||
-workDir "/opt/jenkins-agent"
|
-workDir "/opt/jenkins-agent"
|
||||||
@@ -80,10 +136,31 @@ Jenkins queda disponible en `http://<server-ip>:8080` (admin / admin123).
|
|||||||
|
|
||||||
## Ejecutar un deploy
|
## Ejecutar un deploy
|
||||||
|
|
||||||
1. En Jenkins, abrir el job `deploy-ralph`.
|
1. En Jenkins, abrir el job `deploy-ralph` (o `deploy-lab` para el entorno de laboratorio).
|
||||||
2. Click en **Build with Parameters**.
|
2. Click en **Build with Parameters**.
|
||||||
3. Seleccionar la **tienda** destino en el dropdown (yabucoa / san-lorenzo / rio-grande).
|
3. Seleccionar la **tienda** destino en el dropdown (yabucoa / san-lorenzo / rio-grande para `deploy-ralph`; oficina para `deploy-lab`).
|
||||||
4. El pipeline despliega `install.sh ralph` en paralelo en todos los SCOs online de esa tienda.
|
4. Opcionalmente, completar **NODO** con el nombre exacto de un SCO (ej. `ralph-yabucoa-sco05`) para deployar solo en ese nodo. Si se deja vacío, despliega en paralelo en todos los SCOs online de la tienda seleccionada.
|
||||||
|
|
||||||
|
También se puede disparar desde consola con el Jenkins CLI o `curl`, ver [Ejecutar un job desde consola](#ejecutar-un-job-desde-consola).
|
||||||
|
|
||||||
|
### Ejecutar un job desde consola
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -O https://jenkins.laoficina1782.com/jnlpJars/jenkins-cli.jar
|
||||||
|
|
||||||
|
java -jar jenkins-cli.jar -s https://jenkins.laoficina1782.com/ \
|
||||||
|
-auth TU_USUARIO:TU_API_TOKEN \
|
||||||
|
build deploy-lab -p TIENDA=oficina -s -v
|
||||||
|
|
||||||
|
# Deploy a un solo nodo de la tienda
|
||||||
|
java -jar jenkins-cli.jar -s https://jenkins.laoficina1782.com/ \
|
||||||
|
-auth TU_USUARIO:TU_API_TOKEN \
|
||||||
|
build deploy-lab -p TIENDA=oficina -p NODO=lab-oficina-sco-77 -s -v
|
||||||
|
```
|
||||||
|
|
||||||
|
- `-s` espera a que termine el build; `-v` muestra el log en consola.
|
||||||
|
- El API Token se genera en el usuario de Jenkins → Configure → API Token.
|
||||||
|
- Alternativa sin el CLI: `curl -X POST https://jenkins.laoficina1782.com/job/deploy-lab/buildWithParameters --user TU_USUARIO:TU_API_TOKEN --data-urlencode "TIENDA=oficina"`.
|
||||||
|
|
||||||
## Qué hace `install.sh`
|
## Qué hace `install.sh`
|
||||||
|
|
||||||
@@ -100,8 +177,73 @@ Recibe el nombre del cliente como argumento (`$1`):
|
|||||||
9. Inicializa carpetas y archivos de base de datos del cpi-server.
|
9. Inicializa carpetas y archivos de base de datos del cpi-server.
|
||||||
10. Genera `engine.properties` para SymmetricDS (solo en primera instalación).
|
10. Genera `engine.properties` para SymmetricDS (solo en primera instalación).
|
||||||
|
|
||||||
|
## Scripts de alta (create-node.sh / create-client.sh)
|
||||||
|
|
||||||
|
`data/jenkins/scripts/` tiene dos scripts para no tener que editar `jenkins.yaml`, los Jenkinsfiles y `docker-compose.yml` a mano.
|
||||||
|
|
||||||
|
**Prerequisito**: [`yq`](https://github.com/mikefarah/yq) (la versión de Go, **no** la de Python — son incompatibles, ver más abajo) y que los scripts tengan permiso de ejecución:
|
||||||
|
```sh
|
||||||
|
chmod +x data/jenkins/scripts/*.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Ambos **solo editan archivos** — no hacen `docker compose up -d --build` ni commits. Después de correrlos, revisá con `git diff` y aplicá el rebuild vos mismo.
|
||||||
|
|
||||||
|
### create-node.sh — agregar un nodo a un cliente ya existente
|
||||||
|
|
||||||
|
```sh
|
||||||
|
data/jenkins/scripts/create-node.sh --cliente <cliente> --tienda <tienda> --numero <numero>
|
||||||
|
```
|
||||||
|
|
||||||
|
- Arma el nombre (`<cliente>-<tienda>-sco<numero>`, `numero` normalizado a 3 dígitos) y el label (`sco <cliente> <tienda>`) siguiendo la convención existente, y agrega el nodo al principio de `.jenkins.nodes` en `jenkins.yaml`.
|
||||||
|
- `cliente` y `tienda` deben ser minúsculas, números y guiones (`^[a-z0-9-]+$`) — nada de mayúsculas ni espacios.
|
||||||
|
- Valida que el cliente ya esté dado de alta (que exista `Jenkinsfile.<cliente>`) y que la tienda esté en su dropdown de `TIENDA`. Si falla cualquiera de las dos, corta sin tocar `jenkins.yaml`.
|
||||||
|
- Detecta nodos duplicados (mismo nombre final) y corta sin duplicar.
|
||||||
|
- `--skip-checks` salta las validaciones de cliente/tienda existentes — lo usa `create-client.sh` internamente al dar de alta un cliente nuevo (en ese momento el Jenkinsfile todavía no existe).
|
||||||
|
|
||||||
|
Ejemplo:
|
||||||
|
```sh
|
||||||
|
data/jenkins/scripts/create-node.sh --cliente ralph --tienda yabucoa --numero 8
|
||||||
|
# -> nodo "ralph-yabucoa-sco008", label "sco ralph yabucoa"
|
||||||
|
```
|
||||||
|
|
||||||
|
### create-client.sh — dar de alta un cliente nuevo completo
|
||||||
|
|
||||||
|
```sh
|
||||||
|
data/jenkins/scripts/create-client.sh --cliente <cliente> \
|
||||||
|
--tienda <nombre> [--tienda <nombre> ...] \
|
||||||
|
--nodo <tienda>:<numero> [--nodo <tienda>:<numero> ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `--tienda` es repetible: declara cada tienda que va a aparecer en el dropdown del Jenkinsfile (se deduplica automáticamente si se repite).
|
||||||
|
- `--nodo` es repetible: uno por cada SCO a crear, en formato `<tienda>:<numero>`. Cada `tienda` referenciada en un `--nodo` tiene que haber sido declarada con algún `--tienda`.
|
||||||
|
- Corta antes de tocar nada si el cliente ya existe (ya hay `Jenkinsfile.<cliente>` o ya hay un job `deploy-<cliente>` en `jenkins.yaml`).
|
||||||
|
- Si falla la creación de algún nodo a mitad del loop (ej. `--nodo` repetido), revierte automáticamente los nodos ya creados en esa misma corrida antes de cortar — no deja nodos huérfanos.
|
||||||
|
- Al terminar, genera `data/jenkins/pipelines/Jenkinsfile.<cliente>` (mismo esqueleto que `Jenkinsfile.ralph`, con el parámetro `NODO` opcional), agrega el job `deploy-<cliente>` a `jenkins.yaml` y el volumen correspondiente a `docker-compose.yml`.
|
||||||
|
|
||||||
|
Ejemplo:
|
||||||
|
```sh
|
||||||
|
data/jenkins/scripts/create-client.sh --cliente econo-mbs \
|
||||||
|
--tienda yabucoa --tienda san-lorenzo \
|
||||||
|
--nodo yabucoa:1 --nodo yabucoa:2 --nodo yabucoa:3 \
|
||||||
|
--nodo san-lorenzo:3 --nodo san-lorenzo:4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
- **`usage: yq [-h] [--yaml-output] ...` / `yq: error: argument files: can't open '.jenkins...'`**: tenés instalado el `yq` de Python (kislyuk/yq), que usa sintaxis de `jq` y es incompatible. Desinstalalo (`pip uninstall yq`) e instalá el de Go:
|
||||||
|
```sh
|
||||||
|
wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq
|
||||||
|
chmod +x /usr/local/bin/yq
|
||||||
|
hash -r # si ya habías corrido "yq" antes en esa misma terminal, bash puede tener cacheada la ruta vieja
|
||||||
|
yq --version # debe decir "yq (https://github.com/mikefarah/yq/) version v4..."
|
||||||
|
```
|
||||||
|
- **`Permission denied` al correr un script**: falta el bit de ejecución — `chmod +x data/jenkins/scripts/*.sh`.
|
||||||
|
- **`printf: NNN: invalid octal number`**: no debería pasar (ya está resuelto), pero si aparece es porque un `--numero` con cero a la izquierda se está interpretando como octal en vez de decimal.
|
||||||
|
|
||||||
## Agregar un nuevo cliente
|
## Agregar un nuevo cliente
|
||||||
|
|
||||||
|
Con los scripts (recomendado, ver arriba), o a mano siguiendo estos pasos:
|
||||||
|
|
||||||
1. Agregar los nodos del cliente en `data/jenkins/jenkins.yaml` con el label `sco <cliente> <tienda>`.
|
1. Agregar los nodos del cliente en `data/jenkins/jenkins.yaml` con el label `sco <cliente> <tienda>`.
|
||||||
2. Crear `data/jenkins/pipelines/Jenkinsfile.<cliente>` con el dropdown de tiendas.
|
2. Crear `data/jenkins/pipelines/Jenkinsfile.<cliente>` con el dropdown de tiendas.
|
||||||
3. Agregar el job en la sección `jobs` de `jenkins.yaml`.
|
3. Agregar el job en la sección `jobs` de `jenkins.yaml`.
|
||||||
|
|||||||
+67
-17
@@ -1,5 +1,16 @@
|
|||||||
jenkins:
|
jenkins:
|
||||||
numExecutors: 2
|
numExecutors: 2
|
||||||
|
nodeMonitors:
|
||||||
|
- "architecture"
|
||||||
|
- "clock"
|
||||||
|
- diskSpace:
|
||||||
|
freeSpaceThreshold: "0"
|
||||||
|
freeSpaceWarningThreshold: "0"
|
||||||
|
- "swapSpace"
|
||||||
|
- tmpSpace:
|
||||||
|
freeSpaceThreshold: "0"
|
||||||
|
freeSpaceWarningThreshold: "0"
|
||||||
|
- "responseTime"
|
||||||
authorizationStrategy:
|
authorizationStrategy:
|
||||||
loggedInUsersCanDoAnything:
|
loggedInUsersCanDoAnything:
|
||||||
allowAnonymousRead: false
|
allowAnonymousRead: false
|
||||||
@@ -10,7 +21,7 @@ jenkins:
|
|||||||
- id: admin
|
- id: admin
|
||||||
password: H0l4mund0!
|
password: H0l4mund0!
|
||||||
|
|
||||||
# Agentes permanentes (inbound/JNLP): cada SCO conecta outbound a Jenkins.
|
# Agentes permanentes (WebSocket): cada SCO conecta outbound a Jenkins via wss://.
|
||||||
# Nombre = identificador en Jenkins. Label "sco" agrupa todos los SCOs,
|
# Nombre = identificador en Jenkins. Label "sco" agrupa todos los SCOs,
|
||||||
# luego cliente (ralph) y tienda (yabucoa / san-lorenzo / rio-grande).
|
# luego cliente (ralph) y tienda (yabucoa / san-lorenzo / rio-grande).
|
||||||
nodes:
|
nodes:
|
||||||
@@ -21,7 +32,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-yabucoa-sco03"
|
name: "ralph-yabucoa-sco03"
|
||||||
labelString: "sco ralph yabucoa"
|
labelString: "sco ralph yabucoa"
|
||||||
@@ -29,7 +41,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-yabucoa-sco04"
|
name: "ralph-yabucoa-sco04"
|
||||||
labelString: "sco ralph yabucoa"
|
labelString: "sco ralph yabucoa"
|
||||||
@@ -37,7 +50,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-yabucoa-sco05"
|
name: "ralph-yabucoa-sco05"
|
||||||
labelString: "sco ralph yabucoa"
|
labelString: "sco ralph yabucoa"
|
||||||
@@ -45,7 +59,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-yabucoa-sco06"
|
name: "ralph-yabucoa-sco06"
|
||||||
labelString: "sco ralph yabucoa"
|
labelString: "sco ralph yabucoa"
|
||||||
@@ -53,7 +68,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-yabucoa-sco07"
|
name: "ralph-yabucoa-sco07"
|
||||||
labelString: "sco ralph yabucoa"
|
labelString: "sco ralph yabucoa"
|
||||||
@@ -61,7 +77,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-san-lorenzo-sco03"
|
name: "ralph-san-lorenzo-sco03"
|
||||||
labelString: "sco ralph san-lorenzo"
|
labelString: "sco ralph san-lorenzo"
|
||||||
@@ -69,7 +86,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-san-lorenzo-sco04"
|
name: "ralph-san-lorenzo-sco04"
|
||||||
labelString: "sco ralph san-lorenzo"
|
labelString: "sco ralph san-lorenzo"
|
||||||
@@ -77,7 +95,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-san-lorenzo-sco05"
|
name: "ralph-san-lorenzo-sco05"
|
||||||
labelString: "sco ralph san-lorenzo"
|
labelString: "sco ralph san-lorenzo"
|
||||||
@@ -85,7 +104,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-san-lorenzo-sco06"
|
name: "ralph-san-lorenzo-sco06"
|
||||||
labelString: "sco ralph san-lorenzo"
|
labelString: "sco ralph san-lorenzo"
|
||||||
@@ -93,7 +113,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-rio-grande-sco01"
|
name: "ralph-rio-grande-sco01"
|
||||||
labelString: "sco ralph rio-grande"
|
labelString: "sco ralph rio-grande"
|
||||||
@@ -101,7 +122,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-rio-grande-sco02"
|
name: "ralph-rio-grande-sco02"
|
||||||
labelString: "sco ralph rio-grande"
|
labelString: "sco ralph rio-grande"
|
||||||
@@ -109,7 +131,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-rio-grande-sco03"
|
name: "ralph-rio-grande-sco03"
|
||||||
labelString: "sco ralph rio-grande"
|
labelString: "sco ralph rio-grande"
|
||||||
@@ -117,7 +140,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-rio-grande-sco04"
|
name: "ralph-rio-grande-sco04"
|
||||||
labelString: "sco ralph rio-grande"
|
labelString: "sco ralph rio-grande"
|
||||||
@@ -125,7 +149,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-rio-grande-sco05"
|
name: "ralph-rio-grande-sco05"
|
||||||
labelString: "sco ralph rio-grande"
|
labelString: "sco ralph rio-grande"
|
||||||
@@ -133,7 +158,8 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
- permanent:
|
- permanent:
|
||||||
name: "ralph-rio-grande-sco06"
|
name: "ralph-rio-grande-sco06"
|
||||||
labelString: "sco ralph rio-grande"
|
labelString: "sco ralph rio-grande"
|
||||||
@@ -141,7 +167,21 @@ jenkins:
|
|||||||
numExecutors: 1
|
numExecutors: 1
|
||||||
retentionStrategy: "always"
|
retentionStrategy: "always"
|
||||||
launcher:
|
launcher:
|
||||||
inbound: {}
|
inbound:
|
||||||
|
webSocket: true
|
||||||
|
- permanent:
|
||||||
|
name: "lab-oficina-sco-77"
|
||||||
|
labelString: "sco lab oficina"
|
||||||
|
remoteFS: "/opt/jenkins-agent"
|
||||||
|
numExecutors: 1
|
||||||
|
retentionStrategy: "always"
|
||||||
|
launcher:
|
||||||
|
inbound:
|
||||||
|
webSocket: true
|
||||||
|
|
||||||
|
unclassified:
|
||||||
|
location:
|
||||||
|
url: https://jenkins.laoficina1782.com/
|
||||||
|
|
||||||
security:
|
security:
|
||||||
globalJobDslSecurityConfiguration:
|
globalJobDslSecurityConfiguration:
|
||||||
@@ -158,3 +198,13 @@ jobs:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
- script: |
|
||||||
|
pipelineJob('deploy-lab') {
|
||||||
|
description('Deploy de install.sh a los SCOs de Deploy Lab en la tienda seleccionada.')
|
||||||
|
definition {
|
||||||
|
cps {
|
||||||
|
script(new File('/opt/jenkins/Jenkinsfile.lab').text)
|
||||||
|
sandbox(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
pipeline {
|
||||||
|
agent none
|
||||||
|
|
||||||
|
parameters {
|
||||||
|
choice(
|
||||||
|
name: 'TIENDA',
|
||||||
|
choices: ['oficina'],
|
||||||
|
description: 'Tienda destino'
|
||||||
|
)
|
||||||
|
string(
|
||||||
|
name: 'NODO',
|
||||||
|
defaultValue: '',
|
||||||
|
description: 'Nombre exacto del nodo a deployar (vacío = toda la tienda)'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Preparar scripts') {
|
||||||
|
agent { label 'built-in' }
|
||||||
|
steps {
|
||||||
|
sh 'cp /opt/deploy/install.sh .'
|
||||||
|
stash name: 'scripts', includes: 'install.sh'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy') {
|
||||||
|
agent { label 'built-in' }
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
def labelExpr = "lab && ${params.TIENDA}"
|
||||||
|
def nodes = nodesByLabel(label: labelExpr, offline: false)
|
||||||
|
if (params.NODO?.trim()) {
|
||||||
|
def target = params.NODO.trim()
|
||||||
|
if (!nodes.contains(target)) {
|
||||||
|
error "El nodo '${target}' no está online o no pertenece al label: ${labelExpr}"
|
||||||
|
}
|
||||||
|
nodes = [target]
|
||||||
|
}
|
||||||
|
if (nodes.isEmpty()) {
|
||||||
|
error "No hay nodos online con el label: ${labelExpr}"
|
||||||
|
}
|
||||||
|
def deployStages = nodes.collectEntries { nodeName ->
|
||||||
|
["Deploy ${nodeName}": {
|
||||||
|
node(nodeName) {
|
||||||
|
unstash 'scripts'
|
||||||
|
sh "bash install.sh 'lab'"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
parallel deployStages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,11 @@ pipeline {
|
|||||||
choices: ['yabucoa', 'san-lorenzo', 'rio-grande'],
|
choices: ['yabucoa', 'san-lorenzo', 'rio-grande'],
|
||||||
description: 'Tienda destino'
|
description: 'Tienda destino'
|
||||||
)
|
)
|
||||||
|
string(
|
||||||
|
name: 'NODO',
|
||||||
|
defaultValue: '',
|
||||||
|
description: 'Nombre exacto del nodo a deployar (vacío = toda la tienda)'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
stages {
|
stages {
|
||||||
@@ -24,6 +29,13 @@ pipeline {
|
|||||||
script {
|
script {
|
||||||
def labelExpr = "ralph && ${params.TIENDA}"
|
def labelExpr = "ralph && ${params.TIENDA}"
|
||||||
def nodes = nodesByLabel(label: labelExpr, offline: false)
|
def nodes = nodesByLabel(label: labelExpr, offline: false)
|
||||||
|
if (params.NODO?.trim()) {
|
||||||
|
def target = params.NODO.trim()
|
||||||
|
if (!nodes.contains(target)) {
|
||||||
|
error "El nodo '${target}' no está online o no pertenece al label: ${labelExpr}"
|
||||||
|
}
|
||||||
|
nodes = [target]
|
||||||
|
}
|
||||||
if (nodes.isEmpty()) {
|
if (nodes.isEmpty()) {
|
||||||
error "No hay nodos online con el label: ${labelExpr}"
|
error "No hay nodos online con el label: ${labelExpr}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,203 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Da de alta un cliente nuevo: crea sus nodos, su Jenkinsfile, el job en
|
||||||
|
# jenkins.yaml y el volumen en docker-compose.yml.
|
||||||
|
#
|
||||||
|
# Uso:
|
||||||
|
# create-client.sh --cliente <cliente> \
|
||||||
|
# --tienda <nombre> [--tienda <nombre> ...] \
|
||||||
|
# --nodo <tienda>:<numero> [--nodo <tienda>:<numero> ...]
|
||||||
|
#
|
||||||
|
# Ejemplo:
|
||||||
|
# create-client.sh --cliente econo-mbs \
|
||||||
|
# --tienda yabucoa --tienda san-lorenzo \
|
||||||
|
# --nodo yabucoa:1 --nodo yabucoa:2 --nodo yabucoa:3 \
|
||||||
|
# --nodo san-lorenzo:3 --nodo san-lorenzo:4
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
JENKINS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$JENKINS_DIR/../.." && pwd)"
|
||||||
|
JENKINS_YAML="$JENKINS_DIR/jenkins.yaml"
|
||||||
|
PIPELINES_DIR="$JENKINS_DIR/pipelines"
|
||||||
|
DOCKER_COMPOSE="$REPO_ROOT/docker-compose.yml"
|
||||||
|
|
||||||
|
cliente=""
|
||||||
|
tiendas=()
|
||||||
|
nodos=()
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--cliente) cliente="$2"; shift 2 ;;
|
||||||
|
--tienda) tiendas+=("$2"); shift 2 ;;
|
||||||
|
--nodo) nodos+=("$2"); shift 2 ;;
|
||||||
|
*) echo "Argumento desconocido: $1" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
command -v yq >/dev/null 2>&1 || {
|
||||||
|
echo "yq no está instalado. Instalalo (ej. 'apt install yq' o ver https://github.com/mikefarah/yq#install)." >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -n "$cliente" ]] || { echo "Falta --cliente" >&2; exit 1; }
|
||||||
|
[[ "$cliente" =~ ^[a-z0-9-]+$ ]] || { echo "cliente inválido: '$cliente' (solo minúsculas, números y guiones)" >&2; exit 1; }
|
||||||
|
[[ ${#tiendas[@]} -gt 0 ]] || { echo "Falta al menos un --tienda" >&2; exit 1; }
|
||||||
|
[[ ${#nodos[@]} -gt 0 ]] || { echo "Falta al menos un --nodo" >&2; exit 1; }
|
||||||
|
|
||||||
|
for tienda in "${tiendas[@]}"; do
|
||||||
|
[[ "$tienda" =~ ^[a-z0-9-]+$ ]] || { echo "tienda inválida: '$tienda' (solo minúsculas, números y guiones)" >&2; exit 1; }
|
||||||
|
done
|
||||||
|
|
||||||
|
declare -A tiendas_vistas=()
|
||||||
|
tiendas_dedup=()
|
||||||
|
for tienda in "${tiendas[@]}"; do
|
||||||
|
[[ -n "${tiendas_vistas[$tienda]+x}" ]] && continue
|
||||||
|
tiendas_vistas[$tienda]=1
|
||||||
|
tiendas_dedup+=("$tienda")
|
||||||
|
done
|
||||||
|
tiendas=("${tiendas_dedup[@]}")
|
||||||
|
|
||||||
|
jenkinsfile="$PIPELINES_DIR/Jenkinsfile.${cliente}"
|
||||||
|
|
||||||
|
[[ -f "$jenkinsfile" ]] && { echo "Ya existe $jenkinsfile" >&2; exit 1; }
|
||||||
|
if yq eval '.jobs[].script' "$JENKINS_YAML" | grep -q "deploy-${cliente}"; then
|
||||||
|
echo "Ya existe una entrada de job para '${cliente}' en $JENKINS_YAML" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --nodo debe referenciar una tienda ya declarada con --tienda
|
||||||
|
for nodo in "${nodos[@]}"; do
|
||||||
|
nodo_tienda="${nodo%%:*}"
|
||||||
|
nodo_numero="${nodo#*:}"
|
||||||
|
[[ "$nodo_tienda" != "$nodo" && -n "$nodo_numero" ]] || {
|
||||||
|
echo "Formato de --nodo inválido: '$nodo' (esperado <tienda>:<numero>)" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
match=false
|
||||||
|
for tienda in "${tiendas[@]}"; do
|
||||||
|
[[ "$tienda" == "$nodo_tienda" ]] && { match=true; break; }
|
||||||
|
done
|
||||||
|
[[ "$match" == true ]] || {
|
||||||
|
echo "El nodo '$nodo' referencia la tienda '$nodo_tienda', que no fue declarada con --tienda" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
|
||||||
|
rollback_nodos() {
|
||||||
|
local nombre
|
||||||
|
for nombre in "${creados[@]}"; do
|
||||||
|
echo " - revirtiendo nodo: $nombre" >&2
|
||||||
|
NODE_TO_DELETE="$nombre" yq eval -i 'del(.jenkins.nodes[] | select(.permanent.name == strenv(NODE_TO_DELETE)))' "$JENKINS_YAML"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Creando nodos..."
|
||||||
|
creados=()
|
||||||
|
for nodo in "${nodos[@]}"; do
|
||||||
|
nodo_tienda="${nodo%%:*}"
|
||||||
|
nodo_numero="${nodo#*:}"
|
||||||
|
if ! "$SCRIPT_DIR/create-node.sh" --cliente "$cliente" --tienda "$nodo_tienda" --numero "$nodo_numero" --skip-checks; then
|
||||||
|
echo "" >&2
|
||||||
|
echo "Falló la creación de '$nodo'. Revirtiendo los ${#creados[@]} nodo(s) ya creado(s) para '$cliente'..." >&2
|
||||||
|
rollback_nodos
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
nodo_numero_padded=$(printf '%03d' "$((10#$nodo_numero))")
|
||||||
|
creados+=("${cliente}-${nodo_tienda}-sco${nodo_numero_padded}")
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Generado $jenkinsfile..."
|
||||||
|
choices=""
|
||||||
|
for tienda in "${tiendas[@]}"; do
|
||||||
|
choices+="'${tienda}', "
|
||||||
|
done
|
||||||
|
choices="${choices%, }"
|
||||||
|
|
||||||
|
cat > "$jenkinsfile" <<EOF
|
||||||
|
pipeline {
|
||||||
|
agent none
|
||||||
|
|
||||||
|
parameters {
|
||||||
|
choice(
|
||||||
|
name: 'TIENDA',
|
||||||
|
choices: [${choices}],
|
||||||
|
description: 'Tienda destino'
|
||||||
|
)
|
||||||
|
string(
|
||||||
|
name: 'NODO',
|
||||||
|
defaultValue: '',
|
||||||
|
description: 'Nombre exacto del nodo a deployar (vacío = toda la tienda)'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Preparar scripts') {
|
||||||
|
agent { label 'built-in' }
|
||||||
|
steps {
|
||||||
|
sh 'cp /opt/deploy/install.sh .'
|
||||||
|
stash name: 'scripts', includes: 'install.sh'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy') {
|
||||||
|
agent { label 'built-in' }
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
def labelExpr = "${cliente} \${params.TIENDA}"
|
||||||
|
def nodes = nodesByLabel(label: labelExpr, offline: false)
|
||||||
|
if (params.NODO?.trim()) {
|
||||||
|
def target = params.NODO.trim()
|
||||||
|
if (!nodes.contains(target)) {
|
||||||
|
error "El nodo '\${target}' no está online o no pertenece al label: \${labelExpr}"
|
||||||
|
}
|
||||||
|
nodes = [target]
|
||||||
|
}
|
||||||
|
if (nodes.isEmpty()) {
|
||||||
|
error "No hay nodos online con el label: \${labelExpr}"
|
||||||
|
}
|
||||||
|
def deployStages = nodes.collectEntries { nodeName ->
|
||||||
|
["Deploy \${nodeName}": {
|
||||||
|
node(nodeName) {
|
||||||
|
unstash 'scripts'
|
||||||
|
sh "bash install.sh '${cliente}'"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
parallel deployStages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Agregado job 'deploy-${cliente}' a $JENKINS_YAML..."
|
||||||
|
JOB_SCRIPT="pipelineJob('deploy-${cliente}') {
|
||||||
|
description('Deploy de install.sh a los SCOs de ${cliente} en la tienda seleccionada.')
|
||||||
|
definition {
|
||||||
|
cps {
|
||||||
|
script(new File('/opt/jenkins/Jenkinsfile.${cliente}').text)
|
||||||
|
sandbox(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
JOB_SCRIPT="$JOB_SCRIPT" yq eval -i '.jobs += [{"script": strenv(JOB_SCRIPT)}]' "$JENKINS_YAML"
|
||||||
|
yq eval -i '(.jobs[-1].script) style="literal"' "$JENKINS_YAML"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Agregado volumen a $DOCKER_COMPOSE..."
|
||||||
|
VOLUME_LINE="./data/jenkins/pipelines/Jenkinsfile.${cliente}:/opt/jenkins/Jenkinsfile.${cliente}:ro"
|
||||||
|
VOLUME_LINE="$VOLUME_LINE" yq eval -i '.services.jenkins.volumes += [strenv(VOLUME_LINE)]' "$DOCKER_COMPOSE"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Cliente '${cliente}' creado:"
|
||||||
|
echo " - Nodos: ${nodos[*]}"
|
||||||
|
echo " - Jenkinsfile: $jenkinsfile"
|
||||||
|
echo " - Job: deploy-${cliente}"
|
||||||
|
echo " - Volumen agregado en docker-compose.yml"
|
||||||
|
echo ""
|
||||||
|
echo "Aplicá con: docker compose up -d --build"
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Agrega un nodo permanente (SCO) a data/jenkins/jenkins.yaml.
|
||||||
|
#
|
||||||
|
# Uso:
|
||||||
|
# create-node.sh --cliente <cliente> --tienda <tienda> --numero <numero>
|
||||||
|
#
|
||||||
|
# Ejemplo:
|
||||||
|
# create-node.sh --cliente ralph --tienda yabucoa --numero 8
|
||||||
|
# -> nodo "ralph-yabucoa-sco008", label "sco ralph yabucoa"
|
||||||
|
#
|
||||||
|
# Por default valida que el cliente ya esté dado de alta (que exista
|
||||||
|
# Jenkinsfile.<cliente>) y que la tienda esté en su dropdown de TIENDA.
|
||||||
|
# --skip-checks salta ambas validaciones (lo usa create-client.sh al dar
|
||||||
|
# de alta un cliente nuevo, cuando el Jenkinsfile todavía no existe).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
JENKINS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
JENKINS_YAML="$JENKINS_DIR/jenkins.yaml"
|
||||||
|
PIPELINES_DIR="$JENKINS_DIR/pipelines"
|
||||||
|
|
||||||
|
cliente=""
|
||||||
|
tienda=""
|
||||||
|
numero=""
|
||||||
|
skip_checks=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--cliente) cliente="$2"; shift 2 ;;
|
||||||
|
--tienda) tienda="$2"; shift 2 ;;
|
||||||
|
--numero) numero="$2"; shift 2 ;;
|
||||||
|
--skip-checks) skip_checks=true; shift ;;
|
||||||
|
*) echo "Argumento desconocido: $1" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
command -v yq >/dev/null 2>&1 || {
|
||||||
|
echo "yq no está instalado. Instalalo (ej. 'apt install yq' o ver https://github.com/mikefarah/yq#install)." >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -n "$cliente" && -n "$tienda" && -n "$numero" ]] || {
|
||||||
|
echo "Uso: $0 --cliente <cliente> --tienda <tienda> --numero <numero>" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ "$cliente" =~ ^[a-z0-9-]+$ ]] || { echo "cliente inválido: '$cliente' (solo minúsculas, números y guiones)" >&2; exit 1; }
|
||||||
|
[[ "$tienda" =~ ^[a-z0-9-]+$ ]] || { echo "tienda inválida: '$tienda' (solo minúsculas, números y guiones)" >&2; exit 1; }
|
||||||
|
[[ "$numero" =~ ^[0-9]+$ ]] || { echo "numero inválido: '$numero' (solo dígitos)" >&2; exit 1; }
|
||||||
|
|
||||||
|
numero_padded=$(printf '%03d' "$((10#$numero))")
|
||||||
|
node_name="${cliente}-${tienda}-sco${numero_padded}"
|
||||||
|
node_label="sco ${cliente} ${tienda}"
|
||||||
|
|
||||||
|
[[ -f "$JENKINS_YAML" ]] || { echo "No se encontró $JENKINS_YAML" >&2; exit 1; }
|
||||||
|
|
||||||
|
if [[ "$skip_checks" == false ]]; then
|
||||||
|
jenkinsfile="$PIPELINES_DIR/Jenkinsfile.${cliente}"
|
||||||
|
[[ -f "$jenkinsfile" ]] || {
|
||||||
|
echo "El cliente '$cliente' no está dado de alta (no existe $jenkinsfile)." >&2
|
||||||
|
echo "Usá create-client.sh para darlo de alta primero." >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
choices_line=$(grep -m1 "choices:" "$jenkinsfile" || true)
|
||||||
|
if [[ -n "$choices_line" ]] && ! grep -oE "'[a-z0-9-]+'" <<< "$choices_line" | tr -d "'" | grep -qx "$tienda"; then
|
||||||
|
echo "La tienda '$tienda' no está en el dropdown de $jenkinsfile" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if yq eval ".jenkins.nodes[].permanent.name" "$JENKINS_YAML" | grep -qx "$node_name"; then
|
||||||
|
echo "El nodo '$node_name' ya existe en $JENKINS_YAML" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
NODE_NAME="$node_name" NODE_LABEL="$node_label" yq eval -i '
|
||||||
|
.jenkins.nodes += [{
|
||||||
|
"permanent": {
|
||||||
|
"name": strenv(NODE_NAME),
|
||||||
|
"labelString": strenv(NODE_LABEL),
|
||||||
|
"remoteFS": "/opt/jenkins-agent",
|
||||||
|
"numExecutors": 1,
|
||||||
|
"retentionStrategy": "always",
|
||||||
|
"launcher": {
|
||||||
|
"inbound": {
|
||||||
|
"webSocket": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
' "$JENKINS_YAML"
|
||||||
|
|
||||||
|
echo "Nodo creado: $node_name (label: $node_label)"
|
||||||
|
if [[ "$skip_checks" == false ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "Revisá en jenkins.yaml > jenkins > nodes"
|
||||||
|
echo "Aplicá con: docker compose up -d --build"
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user