build: deploy several Quarkus services from one project list
Add deploy-services.sh so a single command runs build-and-push.sh for every path in a project file, keeps going after a failure, and reports which services failed.
This commit is contained in:
@@ -16,6 +16,10 @@ remote Docker daemon. No external registry is required.
|
|||||||
./scripts/build-and-push.sh --remote deploy@prod.example.com
|
./scripts/build-and-push.sh --remote deploy@prod.example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To push every service in one project, pass that project's list file to
|
||||||
|
`./scripts/deploy-services.sh`. It runs `build-and-push.sh` once per path and
|
||||||
|
reports any failures at the end. See `scripts/README.md` for the list format.
|
||||||
|
|
||||||
The script reads `artifactId` and `version` from `pom.xml` for the local build tag
|
The script reads `artifactId` and `version` from `pom.xml` for the local build tag
|
||||||
and image name, builds for `linux/amd64`, and verifies the deploy on the remote.
|
and image name, builds for `linux/amd64`, and verifies the deploy on the remote.
|
||||||
On the remote, durable tags are only:
|
On the remote, durable tags are only:
|
||||||
|
|||||||
@@ -196,3 +196,28 @@ Comprueba que la primera línea del log sea `Image: test-quarkus:1.0.0-SNAPSHOT`
|
|||||||
```
|
```
|
||||||
|
|
||||||
Opcional: instala `xmllint` para que el script lea el POM con XPath (`libxml2-utils` en Debian/Ubuntu).
|
Opcional: instala `xmllint` para que el script lea el POM con XPath (`libxml2-utils` en Debian/Ubuntu).
|
||||||
|
|
||||||
|
## `deploy-services.sh`
|
||||||
|
|
||||||
|
Script independiente, en el mismo directorio que `build-and-push.sh`. No lo modifica: lo ejecuta una vez por cada servicio de un archivo de lista. Si un servicio falla, sigue con el resto y al terminar indica cuáles fallaron. El código de salida es `1` si alguno falló.
|
||||||
|
|
||||||
|
No hay lista por defecto. `--services` es obligatorio, así cada proyecto puede tener su propio archivo.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./scripts/deploy-services.sh --remote prod --services /srv/proyecto-a/services.txt
|
||||||
|
./scripts/deploy-services.sh --remote prod --services /srv/proyecto-b/services.txt --skip-build
|
||||||
|
```
|
||||||
|
|
||||||
|
Formato del archivo: una ruta por línea. Se ignoran las líneas vacías y las que empiezan por `#`. Una ruta relativa se resuelve desde el directorio del archivo de lista, no desde el directorio donde lanzas el script.
|
||||||
|
|
||||||
|
```text
|
||||||
|
# /srv/proyecto-a/services.txt
|
||||||
|
./api
|
||||||
|
./worker
|
||||||
|
```
|
||||||
|
|
||||||
|
Cada ruta es la raíz de un servicio Quarkus (`pom.xml` y `src/main/docker/Dockerfile`). El nombre y el tag de la imagen salen del `pom.xml` de ese servicio.
|
||||||
|
|
||||||
|
Se reenvían a cada servicio: `--ssh-key`, `--ssh-config`, `--skip-build`, `--no-verify` y `--cleanup`. No se aceptan `--name`, `--tag` ni `--project-dir`.
|
||||||
|
|
||||||
|
Ctrl+C interrumpe el lote. Ayuda: `./scripts/deploy-services.sh --help`.
|
||||||
|
|||||||
Executable
+260
@@ -0,0 +1,260 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Run scripts/build-and-push.sh once per service listed in a project file.
|
||||||
|
# Continues after a failure and prints a summary when the batch finishes.
|
||||||
|
#
|
||||||
|
# The list path is required (--services). Relative service paths are resolved
|
||||||
|
# against the directory that contains that file, not the caller's cwd.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
|
||||||
|
readonly SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
|
||||||
|
readonly BUILD_SCRIPT="${SCRIPT_DIR}/build-and-push.sh"
|
||||||
|
|
||||||
|
SERVICES_FILE=""
|
||||||
|
REMOTE=""
|
||||||
|
forward_args=()
|
||||||
|
|
||||||
|
# ---------- logging ----------
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
readonly C_INFO=$'\033[0;36m'
|
||||||
|
readonly C_OK=$'\033[0;32m'
|
||||||
|
readonly C_WARN=$'\033[0;33m'
|
||||||
|
readonly C_ERR=$'\033[0;31m'
|
||||||
|
readonly C_RESET=$'\033[0m'
|
||||||
|
else
|
||||||
|
readonly C_INFO="" C_OK="" C_WARN="" C_ERR="" C_RESET=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
log() { printf '%s[INFO]%s %s\n' "$C_INFO" "$C_RESET" "$*"; }
|
||||||
|
ok() { printf '%s[OK]%s %s\n' "$C_OK" "$C_RESET" "$*"; }
|
||||||
|
warn() { printf '%s[WARN]%s %s\n' "$C_WARN" "$C_RESET" "$*" >&2; }
|
||||||
|
err() { printf '%s[ERROR]%s %s\n' "$C_ERR" "$C_RESET" "$*" >&2; }
|
||||||
|
die() { err "$*"; exit 1; }
|
||||||
|
|
||||||
|
on_error() {
|
||||||
|
local exit_code=$?
|
||||||
|
err "Script aborted on line ${BASH_LINENO[0]} (exit ${exit_code})."
|
||||||
|
exit "$exit_code"
|
||||||
|
}
|
||||||
|
trap on_error ERR
|
||||||
|
|
||||||
|
# ---------- helpers ----------
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $(basename "$0") --remote TARGET --services FILE [options]
|
||||||
|
|
||||||
|
Run build-and-push.sh once for every service listed in FILE. One list file
|
||||||
|
per project: pass a different --services path to deploy another project.
|
||||||
|
A failure does not stop the batch. The exit code is 1 if any service failed.
|
||||||
|
|
||||||
|
Required:
|
||||||
|
--remote TARGET SSH destination forwarded to build-and-push.sh
|
||||||
|
('user@host[:port]' or a ~/.ssh/config alias)
|
||||||
|
--services FILE Service list for one project. One path per line.
|
||||||
|
Blank lines and lines starting with '#' are
|
||||||
|
ignored. Relative paths resolve against the
|
||||||
|
directory that contains FILE.
|
||||||
|
|
||||||
|
Forwarded to each service (build-and-push.sh):
|
||||||
|
--ssh-key PATH Private key file
|
||||||
|
--ssh-config PATH SSH config file
|
||||||
|
--skip-build Push an existing local image
|
||||||
|
--no-verify Skip the remote image check
|
||||||
|
--cleanup Prune the remote without building or pushing
|
||||||
|
|
||||||
|
Not accepted (each service reads its own pom.xml; --project-dir is set
|
||||||
|
from the list):
|
||||||
|
--name, --tag, --project-dir
|
||||||
|
|
||||||
|
Other:
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$(basename "$0") --remote prod --services /srv/proyecto-a/services.txt
|
||||||
|
$(basename "$0") --remote prod --services /srv/proyecto-b/services.txt --skip-build
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
trim() {
|
||||||
|
local value="$1"
|
||||||
|
value="${value#"${value%%[![:space:]]*}"}"
|
||||||
|
value="${value%"${value##*[![:space:]]}"}"
|
||||||
|
printf '%s' "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_value() {
|
||||||
|
[[ $# -ge 2 && -n "${2:-}" && "$2" != -* ]] \
|
||||||
|
|| die "Missing value for $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------- arg parsing ----------
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--services)
|
||||||
|
require_value "$@"
|
||||||
|
SERVICES_FILE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--remote)
|
||||||
|
require_value "$@"
|
||||||
|
REMOTE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--ssh-key|--ssh-config)
|
||||||
|
require_value "$@"
|
||||||
|
forward_args+=("$1" "$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--skip-build|--no-verify|--cleanup)
|
||||||
|
forward_args+=("$1")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--name|--tag|--project-dir)
|
||||||
|
die "$1 is not accepted. Each service uses its own directory and pom.xml."
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "Unknown argument: $1 (use --help)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "$REMOTE" ]] || die "Missing --remote TARGET (use --help)."
|
||||||
|
[[ -n "$SERVICES_FILE" ]] || die "Missing --services FILE (use --help)."
|
||||||
|
[[ -f "$BUILD_SCRIPT" ]] || die "build-and-push.sh not found next to this script: ${BUILD_SCRIPT}"
|
||||||
|
[[ -f "$SERVICES_FILE" ]] || die "Services file not found: ${SERVICES_FILE}"
|
||||||
|
|
||||||
|
SERVICES_FILE="$(readlink -f "$SERVICES_FILE")"
|
||||||
|
SERVICES_DIR="$(cd "$(dirname "$SERVICES_FILE")" && pwd)"
|
||||||
|
|
||||||
|
services=()
|
||||||
|
while IFS= read -r raw_line || [[ -n "$raw_line" ]]; do
|
||||||
|
line="$(trim "$raw_line")"
|
||||||
|
[[ -z "$line" || "$line" == \#* ]] && continue
|
||||||
|
line="${line/#\~/$HOME}"
|
||||||
|
services+=("$line")
|
||||||
|
done < "$SERVICES_FILE"
|
||||||
|
|
||||||
|
[[ ${#services[@]} -gt 0 ]] || die "Services file is empty: ${SERVICES_FILE}"
|
||||||
|
|
||||||
|
run_build() {
|
||||||
|
local project_dir="$1"
|
||||||
|
if [[ ${#forward_args[@]} -eq 0 ]]; then
|
||||||
|
"$BUILD_SCRIPT" --project-dir "$project_dir" --remote "$REMOTE"
|
||||||
|
else
|
||||||
|
"$BUILD_SCRIPT" --project-dir "$project_dir" --remote "$REMOTE" "${forward_args[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_service() {
|
||||||
|
local entry="$1"
|
||||||
|
if [[ "$entry" == /* ]]; then
|
||||||
|
printf '%s' "$entry"
|
||||||
|
else
|
||||||
|
printf '%s' "${SERVICES_DIR}/${entry}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collapse "." and ".." without requiring the path to exist.
|
||||||
|
normalize_path() {
|
||||||
|
local path="$1"
|
||||||
|
local part joined=""
|
||||||
|
local -a stack=()
|
||||||
|
local old_ifs="$IFS"
|
||||||
|
set -f
|
||||||
|
IFS=/
|
||||||
|
# shellcheck disable=SC2206
|
||||||
|
local -a parts=($path)
|
||||||
|
IFS="$old_ifs"
|
||||||
|
set +f
|
||||||
|
for part in "${parts[@]}"; do
|
||||||
|
case "$part" in
|
||||||
|
""|.) ;;
|
||||||
|
..)
|
||||||
|
if [[ ${#stack[@]} -gt 0 ]]; then
|
||||||
|
unset 'stack[${#stack[@]}-1]'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*) stack+=("$part") ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
if [[ ${#stack[@]} -eq 0 ]]; then
|
||||||
|
printf '/'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
for part in "${stack[@]}"; do
|
||||||
|
joined+="/${part}"
|
||||||
|
done
|
||||||
|
printf '%s' "$joined"
|
||||||
|
}
|
||||||
|
|
||||||
|
total="${#services[@]}"
|
||||||
|
index=0
|
||||||
|
ok_count=0
|
||||||
|
fail_count=0
|
||||||
|
result_paths=()
|
||||||
|
result_notes=()
|
||||||
|
|
||||||
|
log "Services file: ${SERVICES_FILE}"
|
||||||
|
log "Remote: ${REMOTE}"
|
||||||
|
log "Services: ${total}"
|
||||||
|
|
||||||
|
for entry in "${services[@]}"; do
|
||||||
|
index=$((index + 1))
|
||||||
|
resolved="$(normalize_path "$(resolve_service "$entry")")"
|
||||||
|
|
||||||
|
if [[ -d "$resolved" ]]; then
|
||||||
|
resolved="$(cd "$resolved" && pwd)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\n%s========== [%s/%s] %s ==========%s\n' \
|
||||||
|
"$C_INFO" "$index" "$total" "$resolved" "$C_RESET"
|
||||||
|
|
||||||
|
if [[ ! -d "$resolved" ]]; then
|
||||||
|
err "Service directory not found: ${resolved}"
|
||||||
|
result_paths+=("$resolved")
|
||||||
|
result_notes+=("not found")
|
||||||
|
fail_count=$((fail_count + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit_code=0
|
||||||
|
if run_build "$resolved"; then
|
||||||
|
ok "Finished ${resolved}"
|
||||||
|
result_paths+=("$resolved")
|
||||||
|
result_notes+=("0")
|
||||||
|
ok_count=$((ok_count + 1))
|
||||||
|
else
|
||||||
|
exit_code=$?
|
||||||
|
err "Failed ${resolved} (exit ${exit_code})"
|
||||||
|
result_paths+=("$resolved")
|
||||||
|
result_notes+=("exit ${exit_code}")
|
||||||
|
fail_count=$((fail_count + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '\n%s========== Summary ==========%s\n' "$C_INFO" "$C_RESET"
|
||||||
|
summary_index=0
|
||||||
|
while [[ "$summary_index" -lt "$total" ]]; do
|
||||||
|
note="${result_notes[$summary_index]}"
|
||||||
|
path="${result_paths[$summary_index]}"
|
||||||
|
if [[ "$note" == "0" ]]; then
|
||||||
|
ok "${path}"
|
||||||
|
else
|
||||||
|
err "${path} (${note})"
|
||||||
|
fi
|
||||||
|
summary_index=$((summary_index + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$fail_count" -gt 0 ]]; then
|
||||||
|
err "${ok_count} succeeded, ${fail_count} failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ok "${ok_count} succeeded, 0 failed."
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user