add test url
Compose CI / lint-ci-scripts (push) Successful in 5s
Compose CI / compose-verify (push) Successful in 29s

This commit is contained in:
hcornet committed 2026-09-17 15:58:26 +02:00
1 parent 4d407a963f
commit 9b4388d142
2 files changed
+149 -1

No files matched your search

+31 -1
View File
@@ -34,6 +34,10 @@ DROPPED_LABEL_PREFIXES = (
"com.centurylinklabs.watchtower.",
"sablier.",
)
# Cible du test HTTP, déduite des labels Traefik avant leur suppression.
HTTP_TARGET_LABEL = "tips-of-mine.ci.http"
HTTP_PORT_RE = re.compile(r"^traefik\.http\.services\.([^.]+)\.loadbalancer\.server\.port$")
HTTP_SCHEME_RE = re.compile(r"^traefik\.http\.services\.([^.]+)\.loadbalancer\.server\.scheme$")
CI_LABELS = {
"traefik.enable": "false",
"com.centurylinklabs.watchtower.enable": "false",
@@ -268,12 +272,38 @@ def relocate_source(source, settings, create_missing, owner=None):
return posixpath.join(settings["host_dir"], rel)
def http_target(labels):
"""Cibles HTTP d'un service exposé par Traefik : "http:8200,https:8443", "auto" ou None."""
if not any(key.startswith("traefik.http.") for key in labels):
return None
if str(labels.get("traefik.enable", "")).strip().lower() == "false":
return None
ports, schemes = {}, {}
for key, value in labels.items():
match = HTTP_PORT_RE.match(key)
if match:
ports[match.group(1)] = str(value).strip()
match = HTTP_SCHEME_RE.match(key)
if match:
schemes[match.group(1)] = str(value).strip().lower()
targets = set()
for name, port in ports.items():
if port.isdigit():
scheme = "https" if schemes.get(name) == "https" else "http"
targets.add(f"{scheme}:{port}")
return ",".join(sorted(targets)) if targets else "auto"
def rewrite_labels(labels, project):
if isinstance(labels, list):
labels = dict(item.split("=", 1) if "=" in item else (item, "") for item in labels)
kept = {k: v for k, v in (labels or {}).items() if not k.startswith(DROPPED_LABEL_PREFIXES)}
labels = labels or {}
target = http_target(labels)
kept = {k: v for k, v in labels.items() if not k.startswith(DROPPED_LABEL_PREFIXES)}
kept.update(CI_LABELS)
kept["tips-of-mine.ci.project"] = project
if target:
kept[HTTP_TARGET_LABEL] = target
return kept
+118
View File
@@ -18,6 +18,8 @@ readonly CI_MARKER_LABEL="tips-of-mine.ci"
readonly STABILIZATION_SECONDS=15
readonly POLL_SECONDS=5
readonly WAIT_TIMEOUT_SECONDS=300
readonly HTTP_TIMEOUT_SECONDS=300
readonly HTTP_TARGET_LABEL="tips-of-mine.ci.http"
readonly MIN_AVAILABLE_MB=3072
readonly LOG_LINES=50
@@ -27,9 +29,51 @@ readonly WORK_DIR="${RUNNER_TEMP:-/tmp}/compose-ci"
readonly CI_FILE="${WORK_DIR}/compose.ci.json"
readonly COUNT_FILE="${WORK_DIR}/services.count"
readonly MASK_FILE="${WORK_DIR}/mask.b64"
readonly STARTED_FILE="${WORK_DIR}/started.epoch"
readonly SENSITIVE_NAME_RE='PASS|PWD|SECRET|TOKEN|KEY|CREDENTIAL|PRIVATE|AUTH|DSN|URI|URL|SALT|COOKIE'
readonly MASK_MIN_LENGTH=6
# Interroge une URL jusqu'à obtenir une réponse HTTP < 500 ou jusqu'à l'échéance.
# Les redirections ne sont pas suivies : une réponse 3xx prouve que l'application répond.
PROBE_PY="$(cat <<'PY'
import os, ssl, sys, time, urllib.error, urllib.request
url = os.environ["PROBE_URL"]
start = float(os.environ["PROBE_START"])
deadline = float(os.environ["PROBE_DEADLINE"])
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args, **kwargs):
return None
opener = urllib.request.build_opener(
NoRedirect, urllib.request.HTTPSHandler(context=ssl._create_unverified_context())
)
last = "aucune réponse"
while True:
code = None
try:
with opener.open(url, timeout=5) as response:
code = response.status
except urllib.error.HTTPError as error:
code = error.code
except Exception as error:
last = str(getattr(error, "reason", "") or type(error).__name__)
if code is not None:
if code < 500:
print(f"HTTP {code} disponible en {time.time() - start:.0f} s")
sys.exit(0)
last = f"HTTP {code}"
if time.time() >= deadline:
print(f"ÉCHEC ({last}) indisponible après {time.time() - start:.0f} s")
sys.exit(1)
time.sleep(2)
PY
)"
readonly PROBE_PY
PROJECT=""
SERVICE_COUNT=0
ENV_FILE=""
@@ -108,6 +152,13 @@ helper() {
docker run --rm --network none "$@"
}
helper_on() {
local network="$1"
shift
[[ "$network" == "${PROJECT}_"* ]] || die "réseau hors du projet de CI refusé : ${network}"
docker run --rm --network "$network" "$@"
}
load_state() {
PROJECT="$(project_name)"
[[ -f "$COUNT_FILE" ]] \
@@ -209,6 +260,7 @@ cmd_up() {
return 0
fi
compose up --detach --build --quiet-pull
date +%s > "$STARTED_FILE"
}
evaluate_state() {
@@ -248,6 +300,71 @@ evaluate_state() {
done
}
single_exposed_port() {
local id="$1" entry
local -a ports=()
for entry in $(docker inspect --format '{{range $p, $v := .Config.ExposedPorts}}{{$p}} {{end}}' "$id"); do
[[ "$entry" == */tcp ]] && ports+=("${entry%/tcp}")
done
(( ${#ports[@]} == 1 )) && printf '%s' "${ports[0]}"
}
probe() {
local network="$1" url="$2" started="$3" result status=0
result="$(helper_on "$network" \
-e PROBE_URL="$url" \
-e PROBE_START="$started" \
-e PROBE_DEADLINE="$(( started + HTTP_TIMEOUT_SECONDS ))" \
"$HELPER_IMAGE" python3 -c "$PROBE_PY" 2>&1)" || status=$?
printf '%-70s %s\n' "$url" "$result"
return "$status"
}
http_checks() {
local started ids id name spec networks network port target scheme tested=0 status=0
local -a targets=()
[[ -f "$STARTED_FILE" ]] || die "heure de démarrage inconnue : l'étape de démarrage n'a pas abouti"
started="$(<"$STARTED_FILE")"
echo
echo "Test HTTP (délai maximal : ${HTTP_TIMEOUT_SECONDS} s après le démarrage)"
mapfile -t ids < <(compose ps --quiet | grep -v '^$' || true)
for id in "${ids[@]}"; do
IFS='|' read -r name spec networks < <(
docker inspect --format \
"{{.Name}}|{{index .Config.Labels \"${HTTP_TARGET_LABEL}\"}}|{{range \$k, \$v := .NetworkSettings.Networks}}{{\$k}} {{end}}" \
"$id"
)
[[ -n "$spec" ]] || continue
name="${name#/}"
network="${networks%% *}"
if [[ -z "$network" ]]; then
echo "${name} : aucun réseau propre, test HTTP ignoré"
continue
fi
if [[ "$spec" == "auto" ]]; then
port="$(single_exposed_port "$id" || true)"
if [[ -z "$port" ]]; then
echo "${name} : port HTTP indéterminable (aucun label de port Traefik, ports exposés multiples ou absents), test HTTP ignoré"
continue
fi
spec="http:${port}"
fi
IFS=',' read -ra targets <<<"$spec"
for target in "${targets[@]}"; do
scheme="${target%%:*}"
port="${target##*:}"
tested=1
probe "$network" "${scheme}://${name}:${port}/" "$started" || status=1
done
done
if (( ! tested )); then
echo "Aucun service exposé par Traefik : test HTTP non applicable"
fi
return "$status"
}
cmd_verify() {
local timeout deadline stable=0
load_state
@@ -269,6 +386,7 @@ cmd_verify() {
if (( stable )); then
printf '%s' "$STATE_TABLE"
echo "Tous les conteneurs sont démarrés et stables"
http_checks || die "au moins un service web ne répond pas dans le délai"
return 0
fi
stable=1