From b65730c45f7686fdcfc6ec3db6bbc9883a108a57 Mon Sep 17 00:00:00 2001 From: Daniel Arroyo Date: Fri, 25 Sep 2026 12:55:43 -0300 Subject: [PATCH] El nombre de la imagen sale de la carpeta del servicio y el Dockerfile depende de si es java o web. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Así se puede desplegar sin pom.xml y ubicar el Dockerfile en la raíz cuando el servicio es web. --- README.md | 6 +++-- scripts/README.md | 23 ++++++++-------- scripts/build-and-push.sh | 53 +++++++++++++++++++++++++++++-------- scripts/deploy-services.sh | 54 +++++++++++++++++++++++++------------- 4 files changed, 94 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 518ef23..9fa9291 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,10 @@ 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 -and image name, builds for `linux/amd64`, and verifies the deploy on the remote. +The script uses the last directory of the service location as the image name +and reads `version` from `pom.xml` for the local build tag (`1.0.0-SNAPSHOT` +if there is no pom). It builds for `linux/amd64` and verifies the deploy on +the remote. On the remote, durable tags are only: - `:latest` — always the most recent successful push diff --git a/scripts/README.md b/scripts/README.md index ca4be64..57e35d2 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -8,8 +8,8 @@ Construye la imagen nativa de Quarkus y la envía al daemon Docker de un host re ### Qué hace el flujo -1. Lee `artifactId` y `version` de [`pom.xml`](../pom.xml) para formar la referencia de imagen (`nombre:tag`), salvo que uses `--name` o `--tag`. -2. Ejecuta `docker build` con [`src/main/docker/Dockerfile`](../src/main/docker/Dockerfile) para la plataforma fija `linux/amd64` (útil si construyes desde un Mac arm64). +1. Toma el nombre de la imagen de la última carpeta de la ubicación del servicio y lee `version` de [`pom.xml`](../pom.xml) para el tag, salvo que uses `--name` o `--tag`. Si no hay `pom.xml`, el tag es `1.0.0-SNAPSHOT`. +2. Ejecuta `docker build` para la plataforma fija `linux/amd64` (útil si construyes desde un Mac arm64). Con `--type java` (el valor por defecto) usa [`src/main/docker/Dockerfile`](../src/main/docker/Dockerfile). Con `--type web` usa `Dockerfile` en la raíz del servicio. 3. Ejecuta `docker pussh` hacia el destino SSH indicado. 4. Comprueba por SSH que la imagen existe en el remoto (salvo `--no-verify`). 5. Muestra un comando `docker run` sugerido para arrancar el contenedor en el remoto. @@ -31,7 +31,7 @@ curl -fsSL https://raw.githubusercontent.com/psviderski/unregistry/main/docker-p chmod +x ~/.docker/cli-plugins/docker-pussh ``` -`xmllint` es opcional: si no está en el PATH, el script obtiene `artifactId` y `version` del POM con `grep`/`sed`. +`xmllint` es opcional: si no está en el PATH y existe `pom.xml`, el script obtiene `version` del POM con `grep`/`sed`. ### Invocación @@ -60,7 +60,7 @@ Ayuda integrada (en inglés): |--------|-------------| | `--remote TARGET` | Destino SSH: `usuario@host[:puerto]` o un alias definido en `~/.ssh/config` (sin `@`). | -**Identidad de la imagen** (por defecto desde `pom.xml`) +**Identidad de la imagen** (el nombre es la última carpeta del servicio; el tag sale de `pom.xml` si existe) | Opción | Descripción | |--------|-------------| @@ -71,13 +71,14 @@ Ayuda integrada (en inglés): | Opción | Descripción | |--------|-------------| -| `--project-dir PATH` | Raíz del proyecto (debe contener `pom.xml`). | +| `--project-dir PATH` | Raíz del servicio. El nombre de la imagen es su última carpeta. | | `PROJECT_DIR` | Misma función vía entorno. | **Build** | Opción | Descripción | |--------|-------------| +| `--type` | Dónde está el Dockerfile. `java` (por defecto): `src/main/docker/Dockerfile`. `web`: `Dockerfile` en la raíz del servicio. | | `--skip-build` | No construye; solo hace `pussh` de una imagen que ya exista en local. La plataforma de build sigue siendo `linux/amd64` cuando no se omite el build. | **SSH** (se reenvían a `docker pussh` y, en la verificación, a `ssh`) @@ -165,10 +166,10 @@ Al terminar con éxito, el script sugiere algo equivalente a: ```shell ssh deploy@prod.example.com \ - "docker run -d --name --restart unless-stopped -p 8080:8080 :" + "docker run -d --name --restart unless-stopped -p 8080:8080 :" ``` -Sustituye `` y `` por los valores que haya usado el build (los del POM o los de `--name` / `--tag`). +Sustituye `` por la última carpeta de la ubicación del servicio (o el valor de `--name`) y `` por el tag del build (el de `pom.xml`, `1.0.0-SNAPSHOT` si no hay POM, o el de `--tag`). ### Solución de problemas @@ -210,16 +211,16 @@ No hay lista por defecto. `--services` es obligatorio, así cada proyecto puede ./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. +Formato del archivo: una ruta por línea y, si hace falta, un segundo campo `java` o `web`. Si el tipo falta, es `java`. 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 +./portal web ``` -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. +`java` usa `src/main/docker/Dockerfile`. `web` usa `Dockerfile` en la raíz del servicio. El nombre de la imagen es la última carpeta de esa ruta. El tag sale de `pom.xml` si existe; si no, es `1.0.0-SNAPSHOT`. -Se reenvían a cada servicio: `--ssh-key`, `--ssh-config`, `--skip-build`, `--no-verify` y `--cleanup`. No se aceptan `--name`, `--tag` ni `--project-dir`. +Se reenvían a cada servicio: `--ssh-key`, `--ssh-config`, `--skip-build`, `--no-verify` y `--cleanup`, más el `--type` de esa línea. No se aceptan `--name`, `--tag`, `--project-dir` ni `--type`. Ctrl+C interrumpe el lote. Ayuda: `./scripts/deploy-services.sh --help`. diff --git a/scripts/build-and-push.sh b/scripts/build-and-push.sh index 1e5eaeb..2261cb0 100755 --- a/scripts/build-and-push.sh +++ b/scripts/build-and-push.sh @@ -3,7 +3,8 @@ # Build the native Quarkus image and push it to a remote Docker host # via a temporary registry:2 on the remote, reached through an SSH tunnel. # -# - Image name/tag default to pom.xml properties (artifactId and version) +# - Image name defaults to the last directory of the service location +# - Image tag defaults to the pom.xml version (1.0.0-SNAPSHOT if there is no pom) # - Builds for linux/amd64 by default (cross-compile from arm64 hosts) # - Transfers layers over the tunnel (no external registry required) # - On the remote, durable tags are :latest and vYYYY.MM.DD-HH.MM (UTC) only @@ -11,7 +12,6 @@ set -euo pipefail IFS=$'\n\t' -readonly DOCKERFILE="src/main/docker/Dockerfile" readonly DEFAULT_PLATFORM="linux/amd64" readonly TEMP_REGISTRY_IMAGE="registry:2" readonly SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)" @@ -22,6 +22,8 @@ IMAGE_TAG="" SSH_KEY="" SSH_CONFIG="" PROJECT_DIR="${PROJECT_DIR:-${SCRIPT_DIR}/..}" +APP_TYPE="java" +DOCKERFILE="" SKIP_BUILD=0 SKIP_VERIFY=0 CLEANUP=0 @@ -63,16 +65,21 @@ Required: or a bare alias resolved from ~/.ssh/config (e.g. 'prod'). See SSH config aliases below. -Image identity (defaults read from pom.xml): - --name NAME Image repository name - --tag TAG Local build tag (from pom.xml by default). On the +Image identity: + --name NAME Image repository name (default: last directory of + the service location) + --tag TAG Local build tag (pom.xml version by default, or + 1.0.0-SNAPSHOT if there is no pom.xml). On the remote only :latest and vYYYY.MM.DD-HH.MM (UTC). Working directory (defaults to the parent of the script's directory): - --project-dir PATH Project root containing pom.xml + --project-dir PATH Service root. The image name is its last directory (override with env PROJECT_DIR) Build options: + --type java|web Dockerfile location. java (default): + src/main/docker/Dockerfile + web: Dockerfile at the service root --skip-build Skip docker build, only push an existing image (platform is fixed to ${DEFAULT_PLATFORM}) @@ -102,6 +109,7 @@ Examples: $(basename "$0") --remote prod # uses 'Host prod' in ~/.ssh/config $(basename "$0") --remote ubuntu@1.2.3.4 --ssh-key ~/.ssh/deploy_ed25519 $(basename "$0") --remote deploy@prod --skip-build --tag 2.0.0 + $(basename "$0") --remote prod --type web $(basename "$0") --remote prod --cleanup /absolute/path/to/test-quarkus/scripts/$(basename "$0") --remote deploy@prod PROJECT_DIR=/srv/proj $(basename "$0") --remote deploy@prod --skip-build @@ -111,16 +119,39 @@ EOF read_pom() { local xpath="$1" fallback="$2" tag="${3:-}" local value="" + if [[ ! -f pom.xml ]]; then + printf '%s' "$fallback" + return 0 + fi if command -v xmllint >/dev/null 2>&1; then value=$(xmllint --xpath "string($xpath)" pom.xml 2>/dev/null || true) fi if [[ -z "$value" && -n "$tag" ]]; then value=$(grep -E "<${tag}>" pom.xml | head -1 \ - | sed -E "s:.*<${tag}>(.*).*:\1:") + | sed -E "s:.*<${tag}>(.*).*:\1:" || true) fi printf '%s' "${value:-$fallback}" } +# java: src/main/docker/Dockerfile; web: Dockerfile at the service root. +resolve_dockerfile() { + case "$APP_TYPE" in + java) DOCKERFILE="src/main/docker/Dockerfile" ;; + web) DOCKERFILE="Dockerfile" ;; + *) die "Unknown --type '$APP_TYPE' (use java or web)." ;; + esac +} + +# Last directory of the service location (cwd must already be that directory). +image_name_from_location() { + local dir + dir="$(pwd)" + dir="${dir%/}" + [[ -n "$dir" && "$dir" != "/" ]] \ + || die "Cannot derive image name from project directory." + printf '%s' "${dir##*/}" +} + require() { command -v "$1" >/dev/null 2>&1 || die "Required command '$1' not found in PATH." } @@ -382,6 +413,7 @@ while [[ $# -gt 0 ]]; do --ssh-key) SSH_KEY="$2"; shift 2 ;; --ssh-config) SSH_CONFIG="$2"; shift 2 ;; --project-dir) PROJECT_DIR="$2"; shift 2 ;; + --type) APP_TYPE="$2"; shift 2 ;; --skip-build) SKIP_BUILD=1; shift ;; --no-verify) SKIP_VERIFY=1; shift ;; --cleanup) CLEANUP=1; shift ;; @@ -391,6 +423,7 @@ while [[ $# -gt 0 ]]; do done [[ -z "$REMOTE" ]] && die "Missing --remote USER@HOST[:PORT] (use --help)." +resolve_dockerfile # ---------- resolve SSH target ---------- resolved=$(resolve_ssh_alias "$REMOTE") @@ -411,8 +444,7 @@ if [[ "$CLEANUP" -eq 1 ]]; then if ! cd "${PROJECT_DIR}" 2>/dev/null; then die "Invalid --project-dir: ${PROJECT_DIR}" fi - [[ -z "$IMAGE_NAME" ]] \ - && IMAGE_NAME=$(read_pom "/*[local-name()='project']/*[local-name()='artifactId']" "test-quarkus" "artifactId") + [[ -z "$IMAGE_NAME" ]] && IMAGE_NAME="$(image_name_from_location)" parse_ssh_target build_ssh_args detect_remote_docker @@ -428,7 +460,7 @@ fi PROJECT_DIR="$(pwd)" # ---------- defaults ---------- -[[ -z "$IMAGE_NAME" ]] && IMAGE_NAME=$(read_pom "/*[local-name()='project']/*[local-name()='artifactId']" "test-quarkus" "artifactId") +[[ -z "$IMAGE_NAME" ]] && IMAGE_NAME="$(image_name_from_location)" [[ -z "$IMAGE_TAG" ]] && IMAGE_TAG=$(read_pom "/*[local-name()='project']/*[local-name()='version']" "1.0.0-SNAPSHOT" "version") IMAGE_REF="${IMAGE_NAME}:${IMAGE_TAG}" @@ -445,7 +477,6 @@ docker info >/dev/null 2>&1 \ || die "Docker daemon is not reachable. Start Docker Desktop (or your daemon) and retry." [[ -f "$DOCKERFILE" ]] || die "Dockerfile not found at ${DOCKERFILE}." -[[ -f pom.xml ]] || die "pom.xml not found in current directory." parse_ssh_target build_ssh_args diff --git a/scripts/deploy-services.sh b/scripts/deploy-services.sh index 4dc3d57..0d01f92 100755 --- a/scripts/deploy-services.sh +++ b/scripts/deploy-services.sh @@ -7,6 +7,7 @@ # # The list path is required (--services). Relative service paths are resolved # against the directory that contains that file, not the caller's cwd. +# An optional second field, java or web, selects the Dockerfile location. set -euo pipefail IFS=$'\n\t' @@ -57,10 +58,11 @@ process id is appended. 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. + --services FILE Service list for one project. One path per line, + with an optional second field: java or web + (default java). 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 @@ -69,9 +71,9 @@ Forwarded to each service (build-and-push.sh): --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 +Not accepted (each service has its own directory; --type comes from the +list, and --project-dir is set from that path): + --name, --tag, --project-dir, --type Other: -h, --help Show this help @@ -116,8 +118,8 @@ while [[ $# -gt 0 ]]; do forward_args+=("$1") shift ;; - --name|--tag|--project-dir) - die "$1 is not accepted. Each service uses its own directory and pom.xml." + --name|--tag|--project-dir|--type) + die "$1 is not accepted. Each service uses its own directory; --type comes from the services file." ;; -h|--help) usage @@ -137,22 +139,36 @@ done SERVICES_FILE="$(readlink -f "$SERVICES_FILE")" SERVICES_DIR="$(cd "$(dirname "$SERVICES_FILE")" && pwd)" -services=() +service_paths=() +service_types=() while IFS= read -r raw_line || [[ -n "$raw_line" ]]; do line="$(trim "$raw_line")" [[ -z "$line" || "$line" == \#* ]] && continue - line="${line/#\~/$HOME}" - services+=("$line") + svc_path="" + svc_type="" + svc_extra="" + IFS=$' \t' read -r svc_path svc_type svc_extra <<< "$line" + [[ -n "$svc_extra" ]] && die "Service line has an extra field: ${line}" + [[ -n "$svc_path" ]] || die "Service line is missing a path: ${line}" + svc_path="${svc_path/#\~/$HOME}" + [[ -z "$svc_type" ]] && svc_type="java" + case "$svc_type" in + java|web) ;; + *) die "Unknown service type '${svc_type}' in ${SERVICES_FILE} (use java or web)." ;; + esac + service_paths+=("$svc_path") + service_types+=("$svc_type") done < "$SERVICES_FILE" -[[ ${#services[@]} -gt 0 ]] || die "Services file is empty: ${SERVICES_FILE}" +[[ ${#service_paths[@]} -gt 0 ]] || die "Services file is empty: ${SERVICES_FILE}" run_build() { local project_dir="$1" + local app_type="$2" if [[ ${#forward_args[@]} -eq 0 ]]; then - "$BUILD_SCRIPT" --project-dir "$project_dir" --remote "$REMOTE" + "$BUILD_SCRIPT" --project-dir "$project_dir" --remote "$REMOTE" --type "$app_type" else - "$BUILD_SCRIPT" --project-dir "$project_dir" --remote "$REMOTE" "${forward_args[@]}" + "$BUILD_SCRIPT" --project-dir "$project_dir" --remote "$REMOTE" --type "$app_type" "${forward_args[@]}" fi } @@ -239,7 +255,7 @@ write_summary_log() { log "Log file: ${log_path}" } -total="${#services[@]}" +total="${#service_paths[@]}" index=0 ok_count=0 fail_count=0 @@ -257,7 +273,9 @@ log "Services file: ${SERVICES_FILE}" log "Remote: ${REMOTE}" log "Services: ${total}" -for entry in "${services[@]}"; do +while [[ "$index" -lt "$total" ]]; do + entry="${service_paths[$index]}" + app_type="${service_types[$index]}" index=$((index + 1)) resolved="$(normalize_path "$(resolve_service "$entry")")" @@ -277,7 +295,7 @@ for entry in "${services[@]}"; do fi exit_code=0 - if run_build "$resolved"; then + if run_build "$resolved" "$app_type"; then ok "Finished ${resolved}" result_paths+=("$resolved") result_notes+=("0")