Загрузка данных


sudo tee /usr/local/bin/watch-gitlab-builds >/dev/null <<'EOF'
#!/usr/bin/env bash

set -uo pipefail

INTERVAL="${1:-3}"
LOG_LINES="${LOG_LINES:-15}"

if ! [[ "${INTERVAL}" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
    echo "Usage: $0 [refresh_interval_seconds]"
    exit 1
fi

# Использовать docker без sudo, если текущий пользователь имеет доступ.
if docker info >/dev/null 2>&1; then
    DOCKER=(docker)
else
    sudo -v || exit 1
    DOCKER=(sudo docker)
fi

cleanup()
{
    printf '\033[?25h'
}

trap cleanup EXIT INT TERM

# Скрыть курсор.
printf '\033[?25l'

while true; do
    mapfile -t CONTAINERS < <(
        "${DOCKER[@]}" ps \
            --format '{{.ID}}|{{.Names}}|{{.Status}}' \
            2>/dev/null |
        awk -F '|' '$2 ~ /^runner-.*-build$/'
    )

    # Очистить экран и поставить курсор в начало.
    printf '\033[2J\033[H'

    printf 'GitLab Runner build monitor\n'
    printf 'Time: %s\n' "$(date '+%Y-%m-%d %H:%M:%S %Z')"
    printf 'Refresh: %s seconds\n' "${INTERVAL}"
    printf 'Active build containers: %d\n' "${#CONTAINERS[@]}"
    printf '%s\n' '================================================================================'

    if [ "${#CONTAINERS[@]}" -eq 0 ]; then
        echo
        echo "Нет активных сборок GitLab Runner."
        echo "Ожидание новой job..."
        sleep "${INTERVAL}"
        continue
    fi

    for CONTAINER_INFO in "${CONTAINERS[@]}"; do
        IFS='|' read -r CID CONTAINER_NAME CONTAINER_STATUS \
            <<< "${CONTAINER_INFO}"

        # Контейнер мог завершиться между docker ps и docker inspect.
        ENVIRONMENT="$(
            "${DOCKER[@]}" inspect \
                --format '{{range .Config.Env}}{{println .}}{{end}}' \
                "${CID}" 2>/dev/null || true
        )"

        if [ -z "${ENVIRONMENT}" ]; then
            continue
        fi

        JOB_NAME="$(
            sed -n 's/^CI_JOB_NAME=//p' <<< "${ENVIRONMENT}" |
            head -n1
        )"

        JOB_ID="$(
            sed -n 's/^CI_JOB_ID=//p' <<< "${ENVIRONMENT}" |
            head -n1
        )"

        PIPELINE_ID="$(
            sed -n 's/^CI_PIPELINE_ID=//p' <<< "${ENVIRONMENT}" |
            head -n1
        )"

        PROJECT_DIR="$(
            sed -n 's/^CI_PROJECT_DIR=//p' <<< "${ENVIRONMENT}" |
            head -n1
        )"

        OUTPUT_DIR="$(
            sed -n 's/^OUTPUT_DIR=//p' <<< "${ENVIRONMENT}" |
            head -n1
        )"

        JOB_NAME="${JOB_NAME:-unknown}"
        JOB_ID="${JOB_ID:-unknown}"
        PIPELINE_ID="${PIPELINE_ID:-unknown}"

        LOG_FILE=""

        if [ -n "${OUTPUT_DIR}" ]; then
            if [[ "${OUTPUT_DIR}" = /* ]]; then
                LOG_FILE="${OUTPUT_DIR}/build.log"
            elif [ -n "${PROJECT_DIR}" ]; then
                LOG_FILE="${PROJECT_DIR}/${OUTPUT_DIR}/build.log"
            fi
        fi

        # Резервный поиск, если OUTPUT_DIR отсутствует в окружении.
        if [ -z "${LOG_FILE}" ]; then
            LOG_FILE="$(
                "${DOCKER[@]}" exec "${CID}" sh -lc '
                    base="${CI_PROJECT_DIR:-/builds}"

                    find "${base}/output" \
                        -maxdepth 3 \
                        -type f \
                        -name build.log \
                        -printf "%T@ %p\n" 2>/dev/null |
                    sort -nr |
                    head -n1 |
                    cut -d" " -f2-
                ' 2>/dev/null || true
            )"
        fi

        STATS="$(
            "${DOCKER[@]}" stats \
                --no-stream \
                --format \
                'CPU={{.CPUPerc}}  MEM={{.MemUsage}}  NET={{.NetIO}}  BLOCK={{.BlockIO}}  PIDS={{.PIDs}}' \
                "${CID}" 2>/dev/null || true
        )"

        printf '\n'
        printf 'JOB: %s\n' "${JOB_NAME}"
        printf 'Job ID: %s    Pipeline ID: %s\n' \
            "${JOB_ID}" "${PIPELINE_ID}"
        printf 'CID: %s\n' "${CID}"
        printf 'Container: %s\n' "${CONTAINER_NAME}"
        printf 'Status: %s\n' "${CONTAINER_STATUS}"
        printf 'Resources: %s\n' "${STATS:-unavailable}"
        printf 'Output: %s\n' "${OUTPUT_DIR:-not set}"
        printf 'Log: %s\n' "${LOG_FILE:-not found}"

        printf '%s\n' '--------------------------------------------------------------------------------'

        if [ -n "${LOG_FILE}" ]; then
            CURRENT_STAGE="$(
                "${DOCKER[@]}" exec "${CID}" sh -lc '
                    log="$1"

                    if [ -f "${log}" ]; then
                        grep "^>>>" "${log}" |
                        tail -n1
                    fi
                ' sh "${LOG_FILE}" 2>/dev/null || true
            )"

            if [ -n "${CURRENT_STAGE}" ]; then
                printf 'CURRENT BUILROOT STAGE:\n%s\n' "${CURRENT_STAGE}"
            else
                echo "CURRENT BUILDROOT STAGE: ещё не определён"
            fi

            printf '\nLAST %s LOG LINES:\n' "${LOG_LINES}"

            "${DOCKER[@]}" exec "${CID}" sh -lc '
                log="$1"
                lines="$2"

                if [ -f "${log}" ]; then
                    tail -n "${lines}" "${log}"
                else
                    echo "build.log ещё не создан"
                fi
            ' sh "${LOG_FILE}" "${LOG_LINES}" 2>/dev/null || true
        else
            echo "build.log пока не найден."
        fi

        printf '\nTOP PROCESSES:\n'

        "${DOCKER[@]}" exec "${CID}" sh -lc '
            ps -eo pid,etime,%cpu,%mem,comm \
                --sort=-%cpu 2>/dev/null |
            head -n7
        ' 2>/dev/null || true

        printf '%s\n' '================================================================================'
    done

    sleep "${INTERVAL}"
done
EOF

sudo chmod +x /usr/local/bin/watch-gitlab-builds