224 lines
6.9 KiB
Bash
224 lines
6.9 KiB
Bash
#!/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
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Uso:
|
|
$(basename "$0") --cliente <cliente> \\
|
|
--tienda <nombre> [--tienda <nombre> ...] \\
|
|
--nodo <tienda>:<numero> [--nodo <tienda>:<numero> ...]
|
|
|
|
Ejemplo:
|
|
$(basename "$0") --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
|
|
EOF
|
|
}
|
|
|
|
if [[ $# -eq 0 ]]; then
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
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"
|