Der Unterschied zwischen "ich habe es behoben" und "der Fix eines anderen traegt" ist das Rueckgrat des Pruefverfahrens aus 2.6.1. Er stand bisher nur in der Anleitung - im Datenbestand und in den Rechten kam beides gleich an. RECHTE bugtracker:manage war ein einziger Schreib-Scope: Wer eine Nachpruefung bestaetigen durfte, durfte auch resolve, update, bulk_update und die Roadmap umschreiben. Fuer einen Pruefstand ist das zu viel - und wer einen Fix schreibt und ihn selbst abnimmt, prueft gegen die Erwartung, die er beim Schreiben hatte. bugtracker:verify deckt jetzt ab, was ein Pruefer braucht: next, claim, release, comment, confirm, report. Nicht mehr. manage schliesst es ein (IMPLIED_SCOPES), vorhandene Token verlieren nichts. "status" bleibt bewusst bei manage. Darueber liesse sich sonst jeder Zustand setzen, auch "resolved" - der Umweg um genau diese Trennung. DATENBESTAND Eine bestaetigte Nachpruefung war nicht von "jemand hat das Item zugemacht" zu unterscheiden: dc-testkit confirm setzte status=closed und schrieb den Rest in einen Kommentar. Damit war die eine Frage, fuer die das Verfahren gebaut ist, nicht beantwortbar - wurde dieses Item je gegen ein ausgeliefertes Paket geprueft? Ein Kommentartext laesst sich nicht zaehlen und nicht filtern. Migration 017 legt verified_at, verified_in_build und verified_by an. Kein eigener Status daneben: Ein bestaetigtes Item IST geschlossen, die Bestaetigung ist eine Eigenschaft dieses Abschlusses. Ein zweiter Wert waere dieselbe Falle, die 016 beim is_roadmap-Flag vermieden hat. verified_in_build steht neben resolved_in_build, weil beide verschiedene Fragen beantworten und auseinandergehen koennen - genau das ist der interessante Fall. Das eine ist die Behauptung des Behebenden, das andere die Beobachtung des Pruefenden. confirm laesst sich nur auf Items anwenden, die jemand als behoben ausgegeben hat (resolved, awaiting_verification, in_verification, fix_insufficient). Ein offenes Item zu "bestaetigen" waere ein Schliessen mit schmueckendem Beiwort - und stuende hinterher als geprueft in einer Statistik, die genau das misst. Antwort: 409 not_verifiable. MCP dc_confirm. Der Adapter hatte dc_next_task und dc_resolve, aber keinen Weg, eine Nachpruefung abzuschliessen; dc_comment kennt nur action_taken, keinen Status. Ein Pruefagent ueber MCP konnte einen Auftrag uebernehmen und dann nichts damit anfangen. 16 Werkzeuge statt 15. TESTKIT dc-testkit confirm nutzt die Aktion und schickt die gepruefte Fassung als Feld. Antwortet der Server mit 404, faellt es auf den alten Weg zurueck und sagt, dass die Fassung dann nur im Kommentar landet. Drei neue e2e-Faelle: dass confirm die gepruefte Fassung festhaelt, dass es nur dort geht, wo es etwas zu bestaetigen gibt, und dass ein verify-Token kommentieren, aber nicht beheben darf. 147 bestanden, 0 uebersprungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1931 lines
72 KiB
Bash
1931 lines
72 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# dc-testkit - Werkzeugkasten fuer Agenten auf der Linux-Testumgebung
|
|
#
|
|
# Zweck: Ein Agent, der auf der geteilten Test-VM arbeitet, soll ohne Rueckfrage
|
|
# an einen Menschen einen Testlauf durchfuehren koennen - Lizenz besorgen, Paket
|
|
# installieren, Update einspielen, Ergebnis melden. Bis hierher brauchte er fuer
|
|
# jeden dieser Schritte jemanden, der im WebUI etwas anklickt.
|
|
#
|
|
# Warum ein Skript und nicht "der Agent ruft die API direkt auf": Weil sonst
|
|
# jeder Agent seine eigene Fassung der immer gleichen fuenf curl-Aufrufe baut,
|
|
# jede mit anderen Vorgaben, anderer Fehlerbehandlung und anderem Umgang mit
|
|
# dem Token. Die Unterschiede faellt niemandem auf, solange nichts schiefgeht -
|
|
# und wenn etwas schiefgeht, ist unklar, ob die Anwendung oder der Aufruf schuld
|
|
# war. Ein Weg, den alle nehmen, ist der einzige, dessen Fehler man findet.
|
|
#
|
|
# Abhaengigkeiten: curl und python3. Beides ist auf der VM vorhanden; jq wird
|
|
# bewusst NICHT vorausgesetzt. Inzwischen ist es dort zwar installiert - aber
|
|
# das Skript soll auf einer frisch aufgesetzten Maschine sofort laufen, ohne
|
|
# dass jemand sie erst fuer alle veraendert. Was vorausgesetzt wird, muss
|
|
# ueberall gelten, nicht nur auf der einen Maschine, die man vor sich hat.
|
|
#
|
|
# Vollstaendige Anleitung: docs/TESTUMGEBUNG.md im Deploymentcenter-Repo.
|
|
|
|
set -euo pipefail
|
|
|
|
# Fassung dieses Skripts. MUSS bei jeder Aenderung hochgezaehlt werden.
|
|
#
|
|
# Warum das eine Regel und keine Empfehlung ist: Das Skript liegt auf jeder
|
|
# Testmaschine als Kopie. Bleibt die Zahl stehen, meldet eine Maschine mit dem
|
|
# Stand von vorletzter Woche dieselbe Fassung wie eine frisch bezogene - und
|
|
# niemand kann erkennen, warum derselbe Aufruf hier scheitert und dort nicht.
|
|
# Genau so ist es passiert: doctor meldete auf einer Maschine "Bereit", waehrend
|
|
# die ausgelieferte Fassung die fehlenden Rechte des Tokens laengst angemeckert
|
|
# haette. Dieselbe Falle, in die vorher die Serverfassung getappt ist (2.0.0 in
|
|
# der Konfiguration, waehrend 2.7.0 lief).
|
|
DC_TESTKIT_VERSION="1.1.0"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Konfiguration
|
|
#
|
|
# Reihenfolge: Schalter auf der Kommandozeile schlaegt Umgebungsvariable
|
|
# schlaegt Konfigurationsdatei schlaegt Vorgabe. Die Datei ist der Ort fuer
|
|
# alles, was auf dieser Maschine dauerhaft gilt - insbesondere das Token, das
|
|
# damit nicht in der Shell-History landet.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
CONFIG_FILE="${DC_TESTKIT_CONFIG:-$HOME/.config/dc-testkit/config}"
|
|
|
|
# Die Datei wird mit "." gelesen, ihre Zeilen sind also gewoehnliche
|
|
# Zuweisungen - und die ueberschrieben, was in der Umgebung stand. Damit war
|
|
# die Rangfolge oben genau verkehrt herum: "DC_PROJECT=x dc-testkit report"
|
|
# meldete stillschweigend in das Projekt aus der Datei. Auf einer Maschine mit
|
|
# mehreren Projekten ist das die teuerste Art von Fehler - es geht nichts
|
|
# kaputt, es landet nur alles am falschen Ort, und niemand sieht es.
|
|
#
|
|
# Deshalb: vorher merken, was in der Umgebung stand, hinterher wieder
|
|
# durchsetzen. Leere Werte zaehlen als "nicht gesetzt" - ein "DC_TOKEN=" in
|
|
# der Umgebung soll das Token aus der Datei nicht loeschen.
|
|
DC_KONFIGURIERBAR='DC_URL DC_TOKEN DC_PROJECT DC_CHANNEL DC_INSTANCE DC_DISPLAY
|
|
DC_PROJECT_DIR DC_MIN_SERVER DC_TESTKIT_URL DC_TESTKIT_STATE'
|
|
|
|
for _dc_name in $DC_KONFIGURIERBAR; do
|
|
printf -v "_dcenv_$_dc_name" '%s' "${!_dc_name-}"
|
|
done
|
|
|
|
if [ -f "$CONFIG_FILE" ]; then
|
|
# shellcheck disable=SC1090
|
|
. "$CONFIG_FILE"
|
|
fi
|
|
|
|
for _dc_name in $DC_KONFIGURIERBAR; do
|
|
_dc_gemerkt="_dcenv_$_dc_name"
|
|
if [ -n "${!_dc_gemerkt-}" ]; then
|
|
printf -v "$_dc_name" '%s' "${!_dc_gemerkt}"
|
|
fi
|
|
done
|
|
unset _dc_name _dc_gemerkt
|
|
|
|
DC_URL="${DC_URL:-https://dc.mhdf.de}"
|
|
DC_URL="${DC_URL%/}"
|
|
DC_PROJECT="${DC_PROJECT:-}"
|
|
DC_TOKEN="${DC_TOKEN:-}"
|
|
DC_CHANNEL="${DC_CHANNEL:-prod}"
|
|
|
|
# Aelteste Fassung des Deploymentcenters, mit der dieses Testkit vollstaendig
|
|
# arbeiten kann. Darunter fehlen /api/license/v1/dev-key (dc-testkit license),
|
|
# der Bezug ueber /testkit/ und /api/tokens/v1/whoami (Rechtepruefung in
|
|
# doctor). Die Zahl stand vorher nur in einer Fehlermeldung mitten im Ablauf -
|
|
# doctor kannte sie nicht und meldete "Bereit" auf einem zu alten Server.
|
|
DC_MIN_SERVER="${DC_MIN_SERVER:-2.6.1}"
|
|
|
|
# Bezugsquelle dieses Skripts. Daraus zieht doctor die ausgelieferte Fassung
|
|
# und selfupdate den neuen Stand. Die Adresse haengt am DC_URL der Instanz:
|
|
# Wer gegen die Testinstanz arbeitet, soll auch deren Testkit bekommen.
|
|
DC_TESTKIT_URL="${DC_TESTKIT_URL:-$DC_URL/testkit/dc-testkit.sh}"
|
|
|
|
# Die Maschinenkennung entscheidet, welchen DEV-Schluessel der Server
|
|
# heraussucht. Sie muss ueber Laeufe hinweg gleich bleiben - sonst sammelt sich
|
|
# je Lauf ein weiterer Schluessel an, bis die Grenze je Projekt greift.
|
|
DC_INSTANCE="${DC_INSTANCE:-$(hostname -s 2>/dev/null || hostname)}"
|
|
|
|
# Virtuelles Display dieses Projekts, fest vergeben (TESTUMGEBUNG.md 5.5).
|
|
# Ohne Vorgabe: keines - dann bleibt die Oberflaechenpruefung ausgeschaltet,
|
|
# statt sich eine Nummer zu nehmen. Genau das macht "xvfb-run -a", und es
|
|
# beginnt bei :99, also auf der Nummer eines anderen Projekts.
|
|
DC_DISPLAY="${DC_DISPLAY:-}"
|
|
|
|
# Verzeichnis des Projekt-Repos auf dieser Maschine. Ist es gesetzt und steht
|
|
# kein DC_TOKEN fest, holt sich das Testkit das Werkbank-Token von dort - aus
|
|
# der .mcp.json, in der es fuer den MCP-Adapter ohnehin schon liegt.
|
|
#
|
|
# Das ist der Ablageort, den die Projekte bereits benutzen: eine Datei je
|
|
# Projekt, per .gitignore ausgeschlossen, mit dem Token in env.DC_TOKEN. Es ein
|
|
# zweites Mal in ~/.config/dc-testkit zu hinterlegen hiesse, dasselbe Geheimnis
|
|
# an zwei Orten zu pflegen - und beim naechsten Wechsel einen davon zu
|
|
# vergessen. Genau das war auf der Test-VM passiert: In der Testkit-Konfiguration
|
|
# lag noch ein altes Laufzeit-Token, waehrend das richtige Werkbank-Token im
|
|
# Projekt langst ein anderes war.
|
|
DC_PROJECT_DIR="${DC_PROJECT_DIR:-}"
|
|
|
|
STATE_ROOT="${DC_TESTKIT_STATE:-$HOME/.local/state/dc-testkit}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Ausgabe
|
|
# ---------------------------------------------------------------------------
|
|
|
|
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
|
|
C_RESET=$'\033[0m'; C_DIM=$'\033[2m'; C_RED=$'\033[31m'
|
|
C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'; C_BLUE=$'\033[34m'
|
|
else
|
|
C_RESET=''; C_DIM=''; C_RED=''; C_GREEN=''; C_YELLOW=''; C_BLUE=''
|
|
fi
|
|
|
|
note() { printf '%s\n' "$*"; }
|
|
step() { printf '%s==>%s %s\n' "$C_BLUE" "$C_RESET" "$*"; }
|
|
ok() { printf '%s ok%s %s\n' "$C_GREEN" "$C_RESET" "$*"; }
|
|
warn() { printf '%swarn%s %s\n' "$C_YELLOW" "$C_RESET" "$*" >&2; }
|
|
|
|
# Wie warn, aber auf stdout. Fuer doctor: Sein Bericht ist das Ergebnis des
|
|
# Befehls, kein Fehlerkanal - das Ergebnis traegt der Rueckgabewert. Ueber SSH
|
|
# werden stdout und stderr getrennt uebertragen und beim Aufrufer verschraenkt;
|
|
# die Befunde standen dann unter der falschen Ueberschrift. Genau so liest ein
|
|
# Pruefagent den Bericht - und ordnet die Zeile dem falschen Schritt zu.
|
|
befund() { printf '%swarn%s %s\n' "$C_YELLOW" "$C_RESET" "$*"; }
|
|
dim() { printf '%s%s%s\n' "$C_DIM" "$*" "$C_RESET"; }
|
|
|
|
die() {
|
|
printf '%sFehler%s %s\n' "$C_RED" "$C_RESET" "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# JSON
|
|
#
|
|
# python3 statt jq: siehe Kopf. Der Pfad wird mit Punkten angegeben,
|
|
# Listenindizes in eckigen Klammern - json_get '.latest_release.version'.
|
|
# Fehlt der Wert, kommt eine leere Zeile und Rueckgabewert 1; das laesst sich
|
|
# mit || unterscheiden von "Wert ist leer".
|
|
# ---------------------------------------------------------------------------
|
|
|
|
json_get() {
|
|
local path="$1"
|
|
python3 -c '
|
|
import json, re, sys
|
|
|
|
path = sys.argv[1].lstrip(".")
|
|
try:
|
|
data = json.load(sys.stdin)
|
|
except Exception:
|
|
sys.exit(2)
|
|
|
|
cur = data
|
|
if path:
|
|
for part in re.findall(r"[^.\[\]]+", path):
|
|
try:
|
|
cur = cur[int(part)] if part.isdigit() and isinstance(cur, list) else cur[part]
|
|
except (KeyError, IndexError, TypeError):
|
|
sys.exit(1)
|
|
|
|
if cur is None:
|
|
sys.exit(1)
|
|
if isinstance(cur, bool):
|
|
print("true" if cur else "false")
|
|
elif isinstance(cur, (dict, list)):
|
|
print(json.dumps(cur, ensure_ascii=False))
|
|
else:
|
|
print(cur)
|
|
' "$path"
|
|
}
|
|
|
|
# Baut ein JSON-Objekt aus Schluessel-Wert-Paaren. Ohne das muesste jeder
|
|
# Aufruf sein JSON von Hand zusammensetzen - und spaetestens bei einem
|
|
# Stacktrace mit Anfuehrungszeichen und Zeilenumbruechen geht das schief.
|
|
json_object() {
|
|
python3 -c '
|
|
import json, sys
|
|
args = sys.argv[1:]
|
|
out = {}
|
|
for i in range(0, len(args) - 1, 2):
|
|
key, value = args[i], args[i + 1]
|
|
if value == "":
|
|
continue
|
|
if value in ("true", "false"):
|
|
out[key] = value == "true"
|
|
elif key in ("line_no", "limit", "ttl_days") and value.lstrip("-").isdigit():
|
|
out[key] = int(value)
|
|
else:
|
|
out[key] = value
|
|
print(json.dumps(out, ensure_ascii=False))
|
|
' "$@"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# HTTP
|
|
#
|
|
# Setzt API_STATUS und API_BODY. Bricht NICHT von selbst ab - der Aufrufer
|
|
# entscheidet, ob ein 404 ein Fehler ist. Bei 429 wird gewartet und wiederholt,
|
|
# mit verdoppeltem Abstand; das ist die im Handbuch verlangte Reaktion und
|
|
# gehoert an genau eine Stelle statt in jeden Aufruf.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
API_STATUS=0
|
|
API_BODY=''
|
|
|
|
api() {
|
|
local method="$1" path="$2" payload="${3:-}"
|
|
local url="$DC_URL$path"
|
|
local attempt=0 delay=2 raw
|
|
|
|
while :; do
|
|
local -a args=(--silent --show-error --location --max-time 60
|
|
-w '\n%{http_code}' -X "$method"
|
|
-H 'Accept: application/json'
|
|
-H "User-Agent: dc-testkit/$DC_TESTKIT_VERSION")
|
|
|
|
[ -n "$DC_TOKEN" ] && args+=(-H "Authorization: Bearer $DC_TOKEN")
|
|
|
|
if [ -n "$payload" ]; then
|
|
args+=(-H 'Content-Type: application/json' --data-binary "$payload")
|
|
fi
|
|
|
|
if ! raw=$(curl "${args[@]}" "$url" 2>&1); then
|
|
API_STATUS=0
|
|
API_BODY="$raw"
|
|
return 1
|
|
fi
|
|
|
|
API_STATUS="${raw##*$'\n'}"
|
|
API_BODY="${raw%$'\n'*}"
|
|
|
|
# 429 und 5xx sind keine Aussage ueber die Sache, sondern ueber den
|
|
# Zeitpunkt. Dreimal wiederholen, dann aufgeben.
|
|
if { [ "$API_STATUS" = "429" ] || [ "$API_STATUS" -ge 500 ] 2>/dev/null; } && [ "$attempt" -lt 3 ]; then
|
|
attempt=$((attempt + 1))
|
|
warn "HTTP $API_STATUS von $path - erneut in ${delay}s (Versuch $attempt/3)"
|
|
sleep "$delay"
|
|
delay=$((delay * 2))
|
|
continue
|
|
fi
|
|
|
|
break
|
|
done
|
|
|
|
[ "$API_STATUS" -ge 200 ] 2>/dev/null && [ "$API_STATUS" -lt 300 ]
|
|
}
|
|
|
|
# Fehlermeldung aus der Antwort ziehen. Der code ist stabil und fuer Programme
|
|
# gedacht, die message fuer Menschen - beide ausgeben, damit ein Agent das eine
|
|
# auswerten und das andere zitieren kann.
|
|
api_error() {
|
|
local code msg
|
|
code=$(printf '%s' "$API_BODY" | json_get '.error.code' 2>/dev/null || true)
|
|
msg=$(printf '%s' "$API_BODY" | json_get '.error.message' 2>/dev/null || true)
|
|
|
|
if [ -n "$code" ]; then
|
|
printf 'HTTP %s [%s] %s' "$API_STATUS" "$code" "$msg"
|
|
else
|
|
printf 'HTTP %s %s' "$API_STATUS" "$(printf '%s' "$API_BODY" | head -c 400)"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Zustand je Projekt
|
|
# ---------------------------------------------------------------------------
|
|
|
|
require_project() {
|
|
[ -n "$DC_PROJECT" ] || die "Kein Projekt gesetzt. DC_PROJECT=<slug> oder --project <slug>."
|
|
}
|
|
|
|
# Liest env.DC_TOKEN aus der .mcp.json eines Projektverzeichnisses.
|
|
#
|
|
# Gesucht wird an den ueblichen Stellen: im Verzeichnis selbst und eine Ebene
|
|
# tiefer - Projekte legen die Datei mal neben die .sln, mal in den Unterordner
|
|
# mit dem gleichnamigen Projekt. Ein Platzhalter wie ${DC_TOKEN} zaehlt nicht
|
|
# als Token; sonst laeuft der Aufruf mit einer Zeichenkette los, die der Server
|
|
# nur mit 401 beantworten kann.
|
|
token_aus_projekt() {
|
|
local wurzel="$1" datei
|
|
|
|
for datei in "$wurzel/.mcp.json" "$wurzel"/*/.mcp.json; do
|
|
[ -f "$datei" ] || continue
|
|
|
|
python3 -c '
|
|
import json, re, sys
|
|
|
|
try:
|
|
with open(sys.argv[1], encoding="utf-8-sig") as fh:
|
|
daten = json.load(fh)
|
|
except Exception:
|
|
sys.exit(1)
|
|
|
|
for server in (daten.get("mcpServers") or {}).values():
|
|
tok = (server.get("env") or {}).get("DC_TOKEN")
|
|
if isinstance(tok, str) and tok and not re.search(r"[$%<]", tok):
|
|
print(tok)
|
|
sys.exit(0)
|
|
|
|
sys.exit(1)
|
|
' "$datei" && return 0
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
# Wird einmal in main() aufgerufen, bevor irgendein Befehl laeuft - damit auch
|
|
# doctor dasselbe Token sieht wie jeder andere Aufruf. Setzt DC_TOKEN_QUELLE
|
|
# fuer die Auskunft: Wo etwas herkommt, gehoert zu einem Bericht dazu.
|
|
DC_TOKEN_QUELLE=''
|
|
|
|
token_aufloesen() {
|
|
if [ -n "$DC_TOKEN" ]; then
|
|
DC_TOKEN_QUELLE="Konfiguration/Umgebung"
|
|
return 0
|
|
fi
|
|
|
|
[ -n "$DC_PROJECT_DIR" ] || return 0
|
|
|
|
DC_TOKEN="$(token_aus_projekt "$DC_PROJECT_DIR" || true)"
|
|
[ -n "$DC_TOKEN" ] && DC_TOKEN_QUELLE="$DC_PROJECT_DIR/.mcp.json"
|
|
|
|
return 0
|
|
}
|
|
|
|
require_token() {
|
|
[ -n "$DC_TOKEN" ] && return 0
|
|
|
|
# Die Meldung nennt beide Wege und sagt ausdruecklich, warum die Datei
|
|
# fehlen KANN, ohne dass etwas kaputt ist. Ohne diesen Satz sucht der
|
|
# Naechste den Fehler im Repo, im Netz oder in seinen Rechten - und findet
|
|
# keinen, weil es keinen gibt.
|
|
printf '%sFehler%s Kein Werkbank-Token.\n\n' "$C_RED" "$C_RESET" >&2
|
|
|
|
if [ -n "$DC_PROJECT_DIR" ]; then
|
|
printf 'In %s liegt keine .mcp.json mit einem DC_TOKEN.\n\n' "$DC_PROJECT_DIR" >&2
|
|
printf 'Das ist kein Fehler im Repo: .mcp.json ist per .gitignore ausgeschlossen\n' >&2
|
|
printf 'und wird ueber Git NICHT mitgeliefert. Auf einer frisch geklonten\n' >&2
|
|
printf 'Maschine fehlt sie deshalb immer.\n\n' >&2
|
|
printf 'Das Dev-Agent-Token dieses Projekts muss von Hand hierher gebracht\n' >&2
|
|
printf 'werden - es steht in der .mcp.json des Projekts auf dem Arbeitsplatz.\n' >&2
|
|
else
|
|
printf 'Zwei Wege, einer genuegt:\n\n' >&2
|
|
printf ' DC_PROJECT_DIR=<pfad zum Projekt-Repo> - Token aus dessen .mcp.json\n' >&2
|
|
printf ' DC_TOKEN=<token> in %s\n' "$CONFIG_FILE" >&2
|
|
fi
|
|
|
|
exit 1
|
|
}
|
|
|
|
state_dir() {
|
|
require_project
|
|
printf '%s/%s' "$STATE_ROOT" "$DC_PROJECT"
|
|
}
|
|
|
|
sandbox_dir() {
|
|
printf '%s/sandbox' "$(state_dir)"
|
|
}
|
|
|
|
agent_bin() {
|
|
printf '%s/update-agent' "$(state_dir)"
|
|
}
|
|
|
|
key_file() {
|
|
printf '%s/license.key' "$(state_dir)"
|
|
}
|
|
|
|
ensure_state() {
|
|
local dir
|
|
dir="$(state_dir)"
|
|
mkdir -p "$dir"
|
|
chmod 700 "$dir" 2>/dev/null || true
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fassungen und Rechte
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Ist $1 aelter als $2? 0 = ja, 1 = nein, 2 = nicht vergleichbar.
|
|
#
|
|
# Nicht "sort -V": das fehlt auf BusyBox-Systemen, und die Antwort waere dort
|
|
# still falsch statt laut fehlend. Ein Vorabkennzeichen (1.2.3-rc1) wird
|
|
# abgeschnitten - fuer die Frage "reicht der Server aus" genuegt das.
|
|
version_lt() {
|
|
local a="${1%%-*}" b="${2%%-*}" i av bv
|
|
|
|
for i in 1 2 3; do
|
|
av=$(printf '%s' "$a" | cut -d. -f$i)
|
|
bv=$(printf '%s' "$b" | cut -d. -f$i)
|
|
av="${av:-0}"; bv="${bv:-0}"
|
|
|
|
case "$av$bv" in
|
|
''|*[!0-9]*) return 2 ;;
|
|
esac
|
|
|
|
[ "$av" -lt "$bv" ] && return 0
|
|
[ "$av" -gt "$bv" ] && return 1
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
# Deckt die Rechteliste $1 (JSON-Array aus whoami) das Recht $2 ab?
|
|
#
|
|
# Die Wildcards muessen mitgedacht werden: "*" deckt alles ab, "bugtracker:*"
|
|
# alles darunter. Sonst meldete das Werkzeug ein Recht als fehlend, das der
|
|
# Server anerkennt - und schickte jemanden ein Token neu bauen, das passt.
|
|
has_scope() {
|
|
local list="$1" want="$2" prefix
|
|
prefix="${2%%:*}"
|
|
|
|
case "$list" in
|
|
*'"*"'*) return 0 ;;
|
|
*"\"$want\""*) return 0 ;;
|
|
*"\"$prefix:*\""*) return 0 ;;
|
|
esac
|
|
|
|
return 1
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Der eigene Stand
|
|
#
|
|
# Das Skript liegt auf jeder Testmaschine als Kopie und altert dort still vor
|
|
# sich hin. Genau daran ist die Rechtepruefung in doctor einmal gescheitert:
|
|
# Sie war laengst geschrieben, aber auf der Maschine lag der Stand von davor -
|
|
# und weil beide dieselbe Fassung meldeten, sah es niemand.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Wo liegt dieses Skript wirklich? readlink loest einen Verweis in ~/bin auf -
|
|
# sonst ersetzte selfupdate den Verweis und nicht die Datei dahinter.
|
|
own_path() {
|
|
local p="$0"
|
|
|
|
if command -v readlink >/dev/null 2>&1 && readlink -f "$p" >/dev/null 2>&1; then
|
|
readlink -f "$p"
|
|
return 0
|
|
fi
|
|
|
|
case "$p" in
|
|
/*) printf '%s\n' "$p" ;;
|
|
*) printf '%s/%s\n' "$(pwd)" "$p" ;;
|
|
esac
|
|
}
|
|
|
|
# Fassung aus einer Skriptdatei lesen - der eigenen oder einer bezogenen.
|
|
testkit_version_of() {
|
|
sed -n 's/^DC_TESTKIT_VERSION="\([^"]*\)".*/\1/p' "$1" | head -n 1
|
|
}
|
|
|
|
# Holt das ausgelieferte Skript nach $1.
|
|
# 0 = da und plausibel, 1 = Bezug fehlgeschlagen, 2 = das ist kein Testkit
|
|
#
|
|
# Der Plausibilitaetstest ist nicht ueberflaessig: Faellt die Adresse aus oder
|
|
# steht dort eine Anmeldeseite, schriebe der Bezug eine HTML-Seite in die
|
|
# Datei. Danach laesst sich nichts mehr aufrufen - auch nicht das selfupdate,
|
|
# das den Schaden beheben koennte.
|
|
fetch_testkit() {
|
|
local ziel="$1"
|
|
|
|
curl -fsSL --max-time 60 "$DC_TESTKIT_URL" -o "$ziel" 2>/dev/null || return 1
|
|
|
|
head -n 1 "$ziel" | grep -q '^#!' || return 2
|
|
grep -q '^DC_TESTKIT_VERSION=' "$ziel" || return 2
|
|
|
|
return 0
|
|
}
|
|
|
|
cmd_selfupdate() {
|
|
local pruefen=0 erzwingen=0
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--pruefen|--check) pruefen=1 ;;
|
|
--force) erzwingen=1 ;;
|
|
*) die "Unbekannter Schalter: $1" ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
local ziel tmp neu rc=0
|
|
ziel="$(own_path)"
|
|
|
|
step "Bezug von $DC_TESTKIT_URL"
|
|
|
|
# Die Zwischendatei muss neben dem Ziel liegen: nur im selben Dateisystem
|
|
# ist das abschliessende mv ein Umbenennen und damit unteilbar. Ueber eine
|
|
# Dateisystemgrenze hinweg kopiert mv Byte fuer Byte - und wer das Skript
|
|
# in genau dem Moment aufruft, bekommt eine halbe Datei.
|
|
tmp="$ziel.neu.$$"
|
|
|
|
fetch_testkit "$tmp" || rc=$?
|
|
case $rc in
|
|
1) rm -f "$tmp"; die "Bezug fehlgeschlagen - erreichbar? $DC_TESTKIT_URL" ;;
|
|
2) rm -f "$tmp"; die "Dort liegt kein Testkit. Nichts veraendert." ;;
|
|
esac
|
|
|
|
neu="$(testkit_version_of "$tmp")"
|
|
|
|
if cmp -s "$tmp" "$ziel"; then
|
|
rm -f "$tmp"
|
|
ok "bereits aktuell (${neu:-?})"
|
|
return 0
|
|
fi
|
|
|
|
# In welche Richtung geht der Unterschied? Davon haengt alles Weitere ab -
|
|
# der Rat genauso wie die Frage, ob ueberhaupt ersetzt werden darf. Eine
|
|
# aeltere Fassung ueber eine neuere zu ziehen ist fast immer ein Versehen:
|
|
# typisch der Fall, dass jemand im Repo am Skript arbeitet und dem Rat
|
|
# "beheben mit selfupdate" folgt. Rueckgaengig machen laesst sich das
|
|
# nicht, wenn die Aenderung noch nirgends sonst liegt.
|
|
local abstieg=0
|
|
version_lt "${neu:-0}" "$DC_TESTKIT_VERSION" || abstieg=$?
|
|
|
|
local gleiche_nummer=0
|
|
[ "$DC_TESTKIT_VERSION" = "${neu:-}" ] && gleiche_nummer=1
|
|
|
|
if [ "$pruefen" = '1' ]; then
|
|
rm -f "$tmp"
|
|
|
|
if [ "$gleiche_nummer" = '1' ]; then
|
|
warn "gleiche Fassung ($DC_TESTKIT_VERSION), aber anderer Inhalt"
|
|
note " Entweder ist diese Kopie von Hand geaendert worden, oder beim"
|
|
note " Ausliefern wurde DC_TESTKIT_VERSION nicht hochgezaehlt."
|
|
return 1
|
|
fi
|
|
|
|
if [ "$abstieg" = '0' ]; then
|
|
ok "neuer als ausgeliefert ($DC_TESTKIT_VERSION > ${neu:-?}) - Entwicklungsstand"
|
|
note " Kein selfupdate noetig; es holte die aeltere Fassung zurueck."
|
|
return 0
|
|
fi
|
|
|
|
warn "veraltet: hier laeuft $DC_TESTKIT_VERSION, ausgeliefert wird ${neu:-?}"
|
|
note " Beziehen mit: dc-testkit selfupdate"
|
|
return 1
|
|
fi
|
|
|
|
if [ "$erzwingen" != '1' ]; then
|
|
if [ "$gleiche_nummer" = '1' ]; then
|
|
rm -f "$tmp"
|
|
warn "Gleiche Fassung ($DC_TESTKIT_VERSION), aber anderer Inhalt."
|
|
die "Welche der beiden neuer ist, sagt hier nichts. Wenn du es weisst: --force"
|
|
fi
|
|
|
|
if [ "$abstieg" = '0' ]; then
|
|
rm -f "$tmp"
|
|
warn "Ausgeliefert wird ${neu:-?} - hier laeuft bereits $DC_TESTKIT_VERSION."
|
|
die "Das waere ein Rueckschritt. Wenn das gewollt ist: --force"
|
|
fi
|
|
fi
|
|
|
|
if [ ! -w "$ziel" ]; then
|
|
rm -f "$tmp"
|
|
die "Nicht beschreibbar: $ziel"
|
|
fi
|
|
|
|
chmod +x "$tmp" 2>/dev/null || true
|
|
|
|
# Eine Sicherung, bevor etwas ersetzt wird. Der Rueckweg muss ohne Netz
|
|
# gehen: Wer sich gerade eine kaputte Fassung geholt hat, kann sie nicht
|
|
# noch einmal herunterladen, um das rueckgaengig zu machen.
|
|
cp -p "$ziel" "$ziel.vorher" 2>/dev/null || true
|
|
|
|
mv "$tmp" "$ziel"
|
|
|
|
ok "$DC_TESTKIT_VERSION -> ${neu:-?}"
|
|
dim " Vorherige Fassung: $ziel.vorher"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Oberflaechenpruefung
|
|
#
|
|
# Der generische Teil des Verfahrens aus TESTUMGEBUNG.md 5.8: ein eigenes
|
|
# Display starten, aufnehmen, wieder abraeumen. Was dazwischen geklickt wird,
|
|
# bleibt projektspezifisch und gehoert nicht hierher.
|
|
#
|
|
# Warum ueberhaupt gekapselt: Drei Fehler wiederholen sich sonst in jedem
|
|
# Eigenbau-Skript - DISPLAY nicht gesetzt (dann landet alles auf :0, dem
|
|
# echten Desktop), "pkill Xvfb" ohne Nummer (das erschlaegt die Sitzungen der
|
|
# anderen Projekte), und Dateinamen ohne Projektpraefix (zwei Laeufe
|
|
# ueberschreiben sich gegenseitig die Aufnahmen).
|
|
# ---------------------------------------------------------------------------
|
|
|
|
require_display() {
|
|
[ -n "$DC_DISPLAY" ] \
|
|
|| die "Kein Display gesetzt. DC_DISPLAY=:<nr> in $CONFIG_FILE - die Nummer steht in TESTUMGEBUNG.md 7."
|
|
|
|
case "$DC_DISPLAY" in
|
|
:0|:1)
|
|
die "$DC_DISPLAY ist der echte Desktop bzw. der Anmeldebildschirm. Nimm die Nummer deines Projekts."
|
|
;;
|
|
:[0-9]*) ;;
|
|
*) die "DC_DISPLAY muss die Form :<nr> haben, nicht \"$DC_DISPLAY\"." ;;
|
|
esac
|
|
}
|
|
|
|
display_nr() {
|
|
printf '%s' "${DC_DISPLAY#:}"
|
|
}
|
|
|
|
# Laeuft auf diesem Display ein Xvfb? 0 = ja.
|
|
display_laeuft() {
|
|
pgrep -f "Xvfb $DC_DISPLAY" >/dev/null 2>&1
|
|
}
|
|
|
|
# Gibt es den Socket, aber keinen Prozess? Dann ist die Nummer scheinbar
|
|
# belegt und in Wahrheit frei - das Ueberbleibsel eines hart beendeten Xvfb.
|
|
display_verwaist() {
|
|
[ -e "/tmp/.X11-unix/X$(display_nr)" ] && ! display_laeuft
|
|
}
|
|
|
|
cmd_display() {
|
|
local aktion="${1:-status}"
|
|
require_display
|
|
|
|
case "$aktion" in
|
|
status)
|
|
step "Display $DC_DISPLAY"
|
|
if display_laeuft; then
|
|
ok "belegt - $(pgrep -af "Xvfb $DC_DISPLAY" | head -1)"
|
|
elif display_verwaist; then
|
|
befund "verwaister Socket /tmp/.X11-unix/X$(display_nr) - kein Prozess dazu"
|
|
note " Aufraeumen: sudo rm -f /tmp/.X11-unix/X$(display_nr)"
|
|
return 1
|
|
else
|
|
ok "frei"
|
|
fi
|
|
|
|
step "Belegt insgesamt"
|
|
ls /tmp/.X11-unix/ 2>/dev/null | tr '\n' ' ' | sed 's/^/ Sockets: /'
|
|
printf '\n'
|
|
pgrep -af Xvfb 2>/dev/null | sed 's/^/ /' || note " kein Xvfb"
|
|
;;
|
|
|
|
start)
|
|
require_project
|
|
local geometrie="${2:-1600x1000x24}"
|
|
|
|
if display_laeuft; then
|
|
ok "Display $DC_DISPLAY laeuft bereits"
|
|
return 0
|
|
fi
|
|
|
|
if display_verwaist; then
|
|
die "Socket /tmp/.X11-unix/X$(display_nr) ohne Prozess. Erst aufraeumen (sudo rm -f), sonst startet Xvfb nicht."
|
|
fi
|
|
|
|
command -v Xvfb >/dev/null 2>&1 || die "Xvfb ist nicht installiert."
|
|
|
|
step "Display $DC_DISPLAY starten ($geometrie)"
|
|
Xvfb "$DC_DISPLAY" -screen 0 "$geometrie" >/dev/null 2>&1 &
|
|
sleep 2
|
|
|
|
display_laeuft || die "Xvfb ist nicht hochgekommen. Ist $DC_DISPLAY anderweitig belegt?"
|
|
ok "laeuft - in eigenen Aufrufen: export DISPLAY=$DC_DISPLAY"
|
|
;;
|
|
|
|
stop)
|
|
if ! display_laeuft; then
|
|
ok "Display $DC_DISPLAY laeuft nicht"
|
|
return 0
|
|
fi
|
|
|
|
# Mit Nummer, nie ohne: "pkill Xvfb" beendet die Sitzungen aller
|
|
# anderen Projekte auf dieser Maschine gleich mit.
|
|
step "Display $DC_DISPLAY beenden"
|
|
pkill -f "Xvfb $DC_DISPLAY" 2>/dev/null || true
|
|
sleep 1
|
|
display_laeuft && die "Xvfb auf $DC_DISPLAY laeuft weiter."
|
|
ok "beendet"
|
|
;;
|
|
|
|
*)
|
|
die "Aufruf: dc-testkit display [status|start [BxHxT]|stop]"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
cmd_shot() {
|
|
require_project
|
|
require_display
|
|
|
|
local name="${1:-}"
|
|
[ -n "$name" ] || die "Aufruf: dc-testkit shot <name> (z.B. dc-testkit shot 1_start)"
|
|
|
|
display_laeuft || die "Auf $DC_DISPLAY laeuft kein Xvfb. Erst: dc-testkit display start"
|
|
command -v import >/dev/null 2>&1 || die "ImageMagick fehlt (import)."
|
|
|
|
# Projektpraefix ist Pflicht, nicht Geschmack: /tmp gehoert allen, und
|
|
# /tmp/1_start.png ueberschreibt die Aufnahme des Nachbarprojekts.
|
|
local ziel="/tmp/${DC_PROJECT}_${name}.png"
|
|
|
|
DISPLAY="$DC_DISPLAY" import -window root "$ziel" \
|
|
|| die "Aufnahme fehlgeschlagen. Zeichnet auf $DC_DISPLAY ueberhaupt etwas?"
|
|
|
|
ok "$ziel"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# doctor - laeuft hier ueberhaupt etwas?
|
|
#
|
|
# Vor dem ersten Testlauf, und als Erstes, wenn etwas unerklaerlich scheitert.
|
|
# Prueft von unten nach oben: Werkzeuge, Netz, Token, Projekt.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
cmd_doctor() {
|
|
local failed=0
|
|
|
|
step "Werkzeuge"
|
|
for tool in curl python3; do
|
|
if command -v "$tool" >/dev/null 2>&1; then
|
|
ok "$tool $(command -v "$tool")"
|
|
else
|
|
befund "$tool fehlt - ohne das geht nichts"
|
|
failed=1
|
|
fi
|
|
done
|
|
|
|
step "Deploymentcenter unter $DC_URL"
|
|
if api GET /api/health; then
|
|
local healthy server_version
|
|
healthy=$(printf '%s' "$API_BODY" | json_get '.healthy' || echo '?')
|
|
server_version=$(printf '%s' "$API_BODY" | json_get '.version' || echo '')
|
|
ok "erreichbar, healthy=$healthy, Fassung ${server_version:-?}"
|
|
|
|
# Die Fassung stand doctor immer schon vor Augen - verglichen wurde sie
|
|
# nur nie. Deshalb meldete er "Bereit" und der erste Schritt, der den
|
|
# Server wirklich braucht, scheiterte danach mit "kennt
|
|
# /api/license/v1/dev-key noch nicht".
|
|
if [ -z "$server_version" ]; then
|
|
befund "der Server nennt keine Fassung - $DC_MIN_SERVER nicht pruefbar"
|
|
else
|
|
# Der Rueckgabewert MUSS mit || aufgefangen werden. Ohne das
|
|
# beendet "set -e" das Skript, sobald version_lt etwas anderes als
|
|
# 0 liefert - also ausgerechnet im Normalfall "Server ist neu
|
|
# genug" (Rueckgabewert 1). doctor stieg dann wortlos nach der
|
|
# Health-Pruefung aus, ohne Token und Projekt je anzusehen.
|
|
local vergleich=0
|
|
version_lt "$server_version" "$DC_MIN_SERVER" || vergleich=$?
|
|
case $vergleich in
|
|
0)
|
|
befund "zu alt: $server_version - gebraucht wird mindestens $DC_MIN_SERVER"
|
|
befund " Es fehlen: dc-testkit license (dev-key), der Bezug ueber /testkit/"
|
|
befund " und die Rechtepruefung. Der Server muss deployt werden."
|
|
failed=1
|
|
;;
|
|
2)
|
|
befund "Fassung \"$server_version\" ist nicht auswertbar - $DC_MIN_SERVER ungeprueft"
|
|
;;
|
|
*)
|
|
ok "erfuellt die Mindestfassung $DC_MIN_SERVER"
|
|
;;
|
|
esac
|
|
fi
|
|
else
|
|
befund "nicht erreichbar: $(api_error)"
|
|
failed=1
|
|
fi
|
|
|
|
# Der eigene Stand. Diese Pruefung gehoert VOR alles Weitere: Meldet ein
|
|
# veraltetes Testkit "Bereit", ist jede folgende Zeile dieses Berichts
|
|
# unter Vorbehalt - sie stammt aus einer Fassung, die niemand mehr liest.
|
|
step "Dieses Testkit"
|
|
dim " Fassung $DC_TESTKIT_VERSION - $(own_path)"
|
|
|
|
local tk_tmp tk_rc=0 tk_neu
|
|
tk_tmp="${TMPDIR:-/tmp}/dc-testkit.pruefung.$$"
|
|
|
|
fetch_testkit "$tk_tmp" || tk_rc=$?
|
|
if [ "$tk_rc" = '0' ]; then
|
|
tk_neu="$(testkit_version_of "$tk_tmp")"
|
|
if cmp -s "$tk_tmp" "$(own_path)"; then
|
|
ok "aktuell - entspricht der ausgelieferten Fassung ${tk_neu:-?}"
|
|
else
|
|
# Drei verschiedene Lagen, und nur eine davon ruft nach selfupdate.
|
|
# Sie zu verwechseln waere teuer: Wer im Repo am Skript arbeitet,
|
|
# haette auf den Rat "beheben mit selfupdate" hin seine eigene,
|
|
# noch nicht ausgelieferte Arbeit ueberschrieben.
|
|
local tk_cmp=0
|
|
version_lt "$DC_TESTKIT_VERSION" "${tk_neu:-0}" || tk_cmp=$?
|
|
case $tk_cmp in
|
|
0)
|
|
befund "veraltet: hier laeuft $DC_TESTKIT_VERSION, ausgeliefert wird ${tk_neu:-?}"
|
|
befund " Beheben mit: dc-testkit selfupdate"
|
|
failed=1
|
|
;;
|
|
1)
|
|
if [ "$DC_TESTKIT_VERSION" = "${tk_neu:-}" ]; then
|
|
# Der stillste Fall und deshalb der gefaehrlichste:
|
|
# gleiche Nummer, anderer Inhalt. Genau daran ist die
|
|
# Rechtepruefung einmal unbemerkt vorbeigelaufen.
|
|
befund "gleiche Fassung ($DC_TESTKIT_VERSION), aber anderer Inhalt"
|
|
befund " Entweder ist diese Kopie von Hand geaendert worden, oder beim"
|
|
befund " Ausliefern wurde DC_TESTKIT_VERSION nicht hochgezaehlt."
|
|
failed=1
|
|
else
|
|
ok "neuer als ausgeliefert ($DC_TESTKIT_VERSION > ${tk_neu:-?}) - Entwicklungsstand"
|
|
dim " Kein selfupdate: das holte die aeltere Fassung zurueck."
|
|
fi
|
|
;;
|
|
*)
|
|
befund "Fassungen nicht vergleichbar: $DC_TESTKIT_VERSION / ${tk_neu:-?}"
|
|
;;
|
|
esac
|
|
fi
|
|
else
|
|
befund "ausgelieferte Fassung nicht abrufbar - der eigene Stand bleibt ungeprueft"
|
|
dim " $DC_TESTKIT_URL"
|
|
fi
|
|
rm -f "$tk_tmp"
|
|
|
|
step "Token"
|
|
if [ -z "$DC_TOKEN" ]; then
|
|
befund "kein Werkbank-Token"
|
|
if [ -n "$DC_PROJECT_DIR" ]; then
|
|
befund " In $DC_PROJECT_DIR liegt keine .mcp.json mit DC_TOKEN."
|
|
befund " Das ist kein Fehler im Repo: .mcp.json ist per .gitignore"
|
|
befund " ausgeschlossen und kommt ueber Git nicht mit. Auf einer frisch"
|
|
befund " geklonten Maschine fehlt sie deshalb immer - das Token muss von"
|
|
befund " Hand hierher, aus der .mcp.json des Projekts auf dem Arbeitsplatz."
|
|
else
|
|
befund " Entweder DC_PROJECT_DIR auf das Projekt-Repo zeigen lassen"
|
|
befund " (Token aus dessen .mcp.json) oder DC_TOKEN in $CONFIG_FILE."
|
|
fi
|
|
failed=1
|
|
elif api GET /api/tokens/v1/whoami; then
|
|
# Fruehere Fassungen meldeten hier nur "gueltig - der Bugtracker
|
|
# antwortet". Das lief auch mit einem Laufzeit-Token durch, dem
|
|
# bugtracker:manage und license:dev fehlten - der Lauf scheiterte dann
|
|
# erst dort, wo die Rechte wirklich gebraucht wurden.
|
|
local tok_name tok_owner tok_project tok_scopes tok_workbench want
|
|
tok_name=$(printf '%s' "$API_BODY" | json_get '.name' || echo '?')
|
|
tok_owner=$(printf '%s' "$API_BODY" | json_get '.owner_type' || echo '?')
|
|
tok_project=$(printf '%s' "$API_BODY" | json_get '.project_slug' || echo '')
|
|
tok_scopes=$(printf '%s' "$API_BODY" | json_get '.scopes' || echo '[]')
|
|
tok_workbench=$(printf '%s' "$API_BODY" | json_get '.is_workbench' || echo 'false')
|
|
|
|
ok "gueltig - \"$tok_name\", Besitzerart $tok_owner, Projekt ${tok_project:-keins}"
|
|
dim " Rechte: $tok_scopes"
|
|
dim " Herkunft: ${DC_TOKEN_QUELLE:-unbekannt}"
|
|
|
|
if [ "$tok_workbench" != 'true' ]; then
|
|
befund "kein Werkbank-Token: Besitzerart ist $tok_owner, gebraucht wird dev_agent"
|
|
befund " Ein Laufzeit-Token darf nicht in fremde Projekte melden (TESTUMGEBUNG.md 4.2)."
|
|
failed=1
|
|
fi
|
|
|
|
for want in bugtracker:manage license:dev updateservice:read; do
|
|
if ! has_scope "$tok_scopes" "$want"; then
|
|
befund "Recht fehlt: $want"
|
|
failed=1
|
|
fi
|
|
done
|
|
|
|
# Ein Token ohne Projektbindung rutschte hier bisher durch: geprueft
|
|
# wurde nur, ob eine vorhandene Bindung zum Projekt passt. Genau so ein
|
|
# Token lag auf der Test-VM - dev_agent, aber project_slug leer.
|
|
#
|
|
# Ohne Bindung ist das Token nicht eingeschraenkt, sondern
|
|
# uneingeschraenkt: Der Server leitet kein Heimatprojekt ab, also ist
|
|
# fuer ihn jede Meldung eine Heimmeldung. Damit faellt der ganze Schutz
|
|
# fuer Fremdmeldungen weg - keine Begruendungspflicht, keine Deckelung
|
|
# auf medium, kein Eingang in triage_external, kein reported_from.
|
|
if [ -z "$tok_project" ]; then
|
|
befund "Token ohne Projektbindung (project_slug ist leer)"
|
|
befund " Damit zaehlt JEDE Meldung als Heimmeldung: ohne Begruendungspflicht,"
|
|
befund " ohne Deckelung auf medium, direkt in die offene Liste des fremden"
|
|
befund " Projekts - und ohne nachweisbare Herkunft. Im WebUI nachtragen."
|
|
failed=1
|
|
elif [ -n "$DC_PROJECT" ] && [ "$tok_project" != "$DC_PROJECT" ]; then
|
|
befund "Token gehoert zu $tok_project, gearbeitet wird an $DC_PROJECT"
|
|
befund " Je Projekt ein eigenes Token - sonst ist die Herkunft einer Meldung nicht mehr ablesbar."
|
|
failed=1
|
|
fi
|
|
elif [ "$API_STATUS" = '404' ]; then
|
|
# Server ohne whoami. Dann bleibt nur die alte, grobe Pruefung - und
|
|
# der ausdrueckliche Hinweis, dass die Rechte ungeprueft sind. Still
|
|
# "gueltig" zu melden waere genau der Fehler, der hier behoben wird.
|
|
if api GET /api/bugtracker/v1/projects; then
|
|
befund "gueltig, aber die Rechte sind ungeprueft"
|
|
befund " /api/tokens/v1/whoami gibt es erst ab $DC_MIN_SERVER."
|
|
else
|
|
befund "Token wird nicht angenommen: $(api_error)"
|
|
failed=1
|
|
fi
|
|
else
|
|
befund "Token wird nicht angenommen: $(api_error)"
|
|
failed=1
|
|
fi
|
|
|
|
step "Projekt"
|
|
if [ -z "$DC_PROJECT" ]; then
|
|
befund "kein Projekt gesetzt (DC_PROJECT)"
|
|
failed=1
|
|
elif [ -z "$DC_TOKEN" ]; then
|
|
# Ohne Token ist die Projektliste nicht abrufbar. Das stillschweigend
|
|
# zu ueberspringen liesse den Eindruck entstehen, das Projekt sei
|
|
# geprueft worden.
|
|
befund "$DC_PROJECT nicht pruefbar - dafuer wird ein Token gebraucht"
|
|
elif api GET /api/bugtracker/v1/projects; then
|
|
if printf '%s' "$API_BODY" | grep -q "\"$DC_PROJECT\""; then
|
|
ok "$DC_PROJECT ist im Deploymentcenter bekannt"
|
|
else
|
|
befund "$DC_PROJECT kommt in der Projektliste nicht vor"
|
|
failed=1
|
|
fi
|
|
else
|
|
befund "Projektliste nicht abrufbar: $(api_error)"
|
|
failed=1
|
|
fi
|
|
|
|
# Die Oberflaechenpruefung ist der zweite Grund, warum es diese Maschine
|
|
# gibt (5.8) - doctor sah sie bisher gar nicht an. Wer sie braucht, merkte
|
|
# das Fehlen erst mitten im Lauf, mit einem "XOpenDisplay failed" statt
|
|
# einer Auskunft.
|
|
step "Oberflaechenpruefung"
|
|
local gui_fehlt=''
|
|
for tool in Xvfb xdotool import convert; do
|
|
command -v "$tool" >/dev/null 2>&1 || gui_fehlt="$gui_fehlt $tool"
|
|
done
|
|
|
|
if [ -n "$gui_fehlt" ]; then
|
|
# Kein failed=1: Ein Projekt ohne Oberflaeche braucht davon nichts.
|
|
# Ein Befund waere hier eine Falschmeldung - aber schweigen darf das
|
|
# Werkzeug auch nicht.
|
|
note " nicht moeglich, es fehlt:$gui_fehlt"
|
|
note " Nur noetig, wenn dieses Projekt eine Oberflaeche hat."
|
|
else
|
|
ok "Xvfb, xdotool, ImageMagick vorhanden"
|
|
fi
|
|
|
|
if [ -z "$DC_DISPLAY" ]; then
|
|
dim " Kein DC_DISPLAY gesetzt - display/shot bleiben aus."
|
|
dim " Die Nummer dieses Projekts steht in TESTUMGEBUNG.md 7."
|
|
else
|
|
case "$DC_DISPLAY" in
|
|
:0|:1)
|
|
befund "DC_DISPLAY=$DC_DISPLAY ist der echte Desktop bzw. der Anmeldebildschirm"
|
|
befund " Darauf zu zeichnen stoert den Menschen, der dort arbeitet."
|
|
failed=1
|
|
;;
|
|
*)
|
|
if display_laeuft; then
|
|
ok "Display $DC_DISPLAY - belegt (vermutlich von einem eigenen Lauf)"
|
|
elif display_verwaist; then
|
|
befund "Display $DC_DISPLAY - verwaister Socket, kein Prozess dazu"
|
|
befund " Die Nummer wirkt belegt und ist frei. Aufraeumen:"
|
|
befund " sudo rm -f /tmp/.X11-unix/X${DC_DISPLAY#:}"
|
|
failed=1
|
|
else
|
|
ok "Display $DC_DISPLAY - frei"
|
|
fi
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
step "Arbeitsplatz"
|
|
if [ -n "$DC_PROJECT" ]; then
|
|
dim " Zustand: $(state_dir)"
|
|
dim " Sandkasten: $(sandbox_dir)"
|
|
dim " Kennung: $DC_INSTANCE"
|
|
fi
|
|
dim " Platz: $(df -Ph "$HOME" | awk 'NR==2 {print $4 " frei"}')"
|
|
|
|
if [ "$failed" -eq 0 ]; then
|
|
printf '\n%sBereit.%s\n' "$C_GREEN" "$C_RESET"
|
|
else
|
|
printf '\n%sNicht bereit - siehe oben.%s\n' "$C_YELLOW" "$C_RESET"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# license - DEV-Schluessel holen, verlaengern, zurueckgeben
|
|
#
|
|
# Der Aufruf ist absichtlich wiederholbar. Er darf am Anfang jedes Laufs
|
|
# stehen: Gibt es fuer diese Maschine schon einen Schluessel, kommt derselbe
|
|
# zurueck und seine Frist wird verlaengert.
|
|
#
|
|
# Der Schluessel wird in eine Datei mit Rechten 600 geschrieben und NICHT
|
|
# ausgegeben. Ausgabe nur auf ausdrueckliches --print - sonst steht er im
|
|
# Terminalprotokoll, im Sitzungsverlauf des Agenten und am Ende in einem
|
|
# Bugreport.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
cmd_license() {
|
|
require_project; require_token; ensure_state
|
|
|
|
local ttl='' mode='issue' print=0
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--ttl) ttl="$2"; shift 2 ;;
|
|
--revoke) mode='revoke'; shift ;;
|
|
--print) print=1; shift ;;
|
|
*) die "Unbekannter Schalter fuer license: $1" ;;
|
|
esac
|
|
done
|
|
|
|
local payload
|
|
payload=$(json_object \
|
|
project_slug "$DC_PROJECT" \
|
|
instance_id "$DC_INSTANCE" \
|
|
ttl_days "$ttl" \
|
|
action_mode "$mode")
|
|
|
|
if ! api POST /api/license/v1/dev-key "$payload"; then
|
|
die "DEV-Schluessel nicht erhalten: $(api_error)"
|
|
fi
|
|
|
|
# Ein Deploymentcenter vor 2.6.1 kennt die Aktion nicht. Es antwortet dann
|
|
# aber NICHT mit 404: Die Lizenz-API faellt bei unbekannten Pfadsegmenten
|
|
# auf 'status' zurueck und liefert HTTP 200 mit {"status":"ok","module":
|
|
# "license"}. Ohne diese Pruefung sucht der erste Agent den Fehler bei sich.
|
|
if [ "$(printf '%s' "$API_BODY" | json_get '.module' 2>/dev/null || true)" = 'license' ]; then
|
|
die "Dieses Deploymentcenter kennt /api/license/v1/dev-key noch nicht.
|
|
Der Server antwortet mit der Verfuegbarkeitspruefung des Lizenzmoduls statt mit
|
|
einem Schluessel - die Fassung ist aelter als 2.6.1. Es muss deployt werden."
|
|
fi
|
|
|
|
if [ "$mode" = 'revoke' ]; then
|
|
rm -f "$(key_file)"
|
|
ok "$(printf '%s' "$API_BODY" | json_get '.message')"
|
|
return 0
|
|
fi
|
|
|
|
local key expires
|
|
key=$(printf '%s' "$API_BODY" | json_get '.license_key') \
|
|
|| die "Antwort enthaelt keinen Schluessel: $API_BODY"
|
|
expires=$(printf '%s' "$API_BODY" | json_get '.expires_at' || echo '?')
|
|
|
|
local file
|
|
file="$(key_file)"
|
|
( umask 077; printf '%s' "$key" > "$file" )
|
|
|
|
ok "$(printf '%s' "$API_BODY" | json_get '.message')"
|
|
dim " gilt bis: $expires UTC"
|
|
dim " Aktivierungen: $(printf '%s' "$API_BODY" | json_get '.activations_used')/$(printf '%s' "$API_BODY" | json_get '.max_activations')"
|
|
dim " abgelegt in: $file"
|
|
|
|
[ "$print" -eq 1 ] && printf '%s\n' "$key"
|
|
return 0
|
|
}
|
|
|
|
load_key() {
|
|
local file
|
|
file="$(key_file)"
|
|
[ -f "$file" ] || die "Kein DEV-Schluessel vorhanden. Zuerst: dc-testkit license"
|
|
cat "$file"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# agent - den update-agent besorgen
|
|
#
|
|
# Mit Pruefsummenabgleich gegen installer.json. Das ist nicht Zierde: Ein
|
|
# Testlauf, der ein halb heruntergeladenes Binary startet, meldet Fehler, die
|
|
# es in der Anwendung nicht gibt.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
cmd_agent() {
|
|
require_project; ensure_state
|
|
|
|
local force=0
|
|
[ "${1:-}" = '--force' ] && force=1
|
|
|
|
local bin
|
|
bin="$(agent_bin)"
|
|
|
|
if [ -x "$bin" ] && [ "$force" -eq 0 ]; then
|
|
ok "update-agent liegt bereits vor ($bin)"
|
|
return 0
|
|
fi
|
|
|
|
local arch platform
|
|
arch="$(uname -m)"
|
|
case "$arch" in
|
|
x86_64|amd64) platform='linux-x64' ;;
|
|
aarch64|arm64) platform='linux-arm64' ;;
|
|
*) die "Fuer die Architektur $arch gibt es kein update-agent-Binary." ;;
|
|
esac
|
|
|
|
step "update-agent fuer $platform holen"
|
|
|
|
local manifest
|
|
manifest=$(curl -fsSL --max-time 60 "$DC_URL/installer/installer.json") \
|
|
|| die "installer.json nicht abrufbar."
|
|
|
|
local expected file idx=0 found=''
|
|
while :; do
|
|
local p
|
|
p=$(printf '%s' "$manifest" | json_get ".binaries[$idx].platform") || break
|
|
if [ "$p" = "$platform" ]; then
|
|
found="$idx"
|
|
break
|
|
fi
|
|
idx=$((idx + 1))
|
|
done
|
|
|
|
[ -n "$found" ] || die "installer.json kennt kein Binary fuer $platform."
|
|
|
|
file=$(printf '%s' "$manifest" | json_get ".binaries[$found].file")
|
|
expected=$(printf '%s' "$manifest" | json_get ".binaries[$found].sha256")
|
|
|
|
curl -fSL --max-time 600 -o "$bin.part" "$DC_URL/installer/$file" \
|
|
|| die "Download von $file fehlgeschlagen."
|
|
|
|
local actual
|
|
actual=$(sha256sum "$bin.part" | cut -d' ' -f1)
|
|
|
|
if [ "$actual" != "$expected" ]; then
|
|
rm -f "$bin.part"
|
|
die "Pruefsumme weicht ab. Erwartet $expected, bekommen $actual."
|
|
fi
|
|
|
|
mv "$bin.part" "$bin"
|
|
chmod +x "$bin"
|
|
|
|
ok "update-agent $(printf '%s' "$manifest" | json_get '.version') abgelegt, Pruefsumme stimmt"
|
|
}
|
|
|
|
# Der Schluessel geht ueber die Umgebung, nicht ueber --license-key.
|
|
#
|
|
# Die Maschine ist geteilt. Was in der Befehlszeile steht, sieht jeder andere
|
|
# Benutzer in "ps aux" - ein Lizenzschluessel gehoert da nicht hin, auch kein
|
|
# befristeter. Die Umgebung eines Prozesses liest dagegen nur, wer ihm gehoert
|
|
# (oder root). Der update-agent nimmt DC_LICENSE_KEY seit jeher entgegen und
|
|
# bevorzugt es sogar gegenueber dem Schalter.
|
|
run_agent() {
|
|
local bin
|
|
bin="$(agent_bin)"
|
|
[ -x "$bin" ] || die "update-agent fehlt. Zuerst: dc-testkit agent"
|
|
|
|
DC_LICENSE_KEY="$(load_key)" "$bin" \
|
|
--base-url "$DC_URL" \
|
|
--project "$DC_PROJECT" \
|
|
--channel "$DC_CHANNEL" \
|
|
"$@"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# check / install / update / repair - die Auslieferungswege
|
|
#
|
|
# ZUM VERSTAENDNIS DES INSTALL-BEFEHLS: "--action install" des update-agent ist
|
|
# ein Dialog mit Auswahllisten und Passwortabfrage. Er laesst sich nicht
|
|
# kopflos treiben, und ein Agent darf ohnehin kein Passwort eingeben. Was hier
|
|
# als "install" laeuft, ist deshalb die Erstauslieferung in ein LEERES
|
|
# Verzeichnis ueber den Update-Weg.
|
|
#
|
|
# Das ist keine Notloesung: Der Installer holt die Dateien laut
|
|
# SETUP_INTEGRATION_GUIDE ueber genau denselben Pfad - Download, SHA256,
|
|
# Signaturpruefung, Staging, Rollback. Was der Dialog zusaetzlich tut, ist
|
|
# fragen und eine Konfiguration schreiben. Geprueft wird hier also die
|
|
# Auslieferung, nicht die Gespraechsfuehrung.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
cmd_check() {
|
|
require_project
|
|
step "Auf Updates pruefen ($DC_PROJECT, Kanal $DC_CHANNEL)"
|
|
run_agent --action check --target-dir "$(sandbox_dir)" "$@"
|
|
}
|
|
|
|
cmd_install() {
|
|
require_project; ensure_state
|
|
|
|
local dir
|
|
dir="$(sandbox_dir)"
|
|
|
|
if [ -d "$dir" ] && [ -n "$(ls -A "$dir" 2>/dev/null)" ]; then
|
|
die "Der Sandkasten $dir ist nicht leer. Erst 'dc-testkit cleanup --sandbox', sonst pruefst du ein Update statt einer Erstinstallation."
|
|
fi
|
|
|
|
mkdir -p "$dir"
|
|
|
|
step "Erstauslieferung nach $dir"
|
|
run_agent --action update --version latest --target-dir "$dir" "$@"
|
|
}
|
|
|
|
cmd_update() {
|
|
require_project
|
|
|
|
local version='latest'
|
|
if [ "${1:-}" = '--version' ]; then
|
|
version="$2"; shift 2
|
|
fi
|
|
|
|
step "Update auf $version"
|
|
run_agent --action update --version "$version" --target-dir "$(sandbox_dir)" "$@"
|
|
}
|
|
|
|
cmd_repair() {
|
|
require_project
|
|
step "Integritaetsreparatur"
|
|
run_agent --action repair --target-dir "$(sandbox_dir)" "$@"
|
|
}
|
|
|
|
cmd_list() {
|
|
require_project
|
|
run_agent --action list "$@"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# verify - was liegt jetzt eigentlich da?
|
|
#
|
|
# Ein "Update erfolgreich" auf der Konsole ist keine Aussage darueber, was im
|
|
# Zielverzeichnis steht. Diese Pruefung liest das Manifest und vergleicht es
|
|
# mit dem, was der Server als neueste Fassung fuehrt.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
cmd_verify() {
|
|
require_project
|
|
|
|
local dir manifest
|
|
dir="$(sandbox_dir)"
|
|
manifest="$dir/manifest.json"
|
|
|
|
step "Installation in $dir pruefen"
|
|
|
|
[ -d "$dir" ] || die "$dir gibt es nicht - es wurde nichts installiert."
|
|
|
|
local files
|
|
files=$(find "$dir" -type f | wc -l)
|
|
dim " Dateien: $files"
|
|
|
|
[ "$files" -gt 0 ] || die "Das Zielverzeichnis ist leer."
|
|
|
|
if [ ! -f "$manifest" ]; then
|
|
warn "Keine manifest.json im Zielverzeichnis."
|
|
warn "Ohne sie meldet der Agent beim naechsten Lauf 'Unbekannt' als installierte Fassung."
|
|
return 1
|
|
fi
|
|
|
|
local installed
|
|
installed=$(json_get '.version' < "$manifest" || echo '')
|
|
[ -n "$installed" ] || die "manifest.json enthaelt keine Version."
|
|
|
|
ok "installiert: $installed"
|
|
|
|
local platform
|
|
case "$(uname -m)" in
|
|
x86_64|amd64) platform='linux-x64' ;;
|
|
aarch64|arm64) platform='linux-arm64' ;;
|
|
*) platform='' ;;
|
|
esac
|
|
|
|
if api GET "/api/updateservice/v1/latest?product=$DC_PROJECT&channel=$DC_CHANNEL&platform=$platform"; then
|
|
local latest
|
|
latest=$(printf '%s' "$API_BODY" | json_get '.latest_release.version' || echo '')
|
|
|
|
if [ -z "$latest" ]; then
|
|
warn "Der Server nennt keine neueste Fassung fuer Kanal $DC_CHANNEL/$platform."
|
|
elif [ "$latest" = "$installed" ]; then
|
|
ok "entspricht der neuesten Fassung auf dem Server"
|
|
else
|
|
warn "Server fuehrt $latest, installiert ist $installed"
|
|
return 1
|
|
fi
|
|
else
|
|
warn "Serverabgleich nicht moeglich: $(api_error)"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# report - Fehler und Wuensche in den Bugtracker
|
|
#
|
|
# client_ref wird aus Projekt, Titel und Fundstelle abgeleitet, nicht
|
|
# gewuerfelt. Damit legt derselbe Befund aus zehn Testlaeufen ein Item an und
|
|
# nicht zehn - genau dafuer ist das Feld da. Wer bewusst ein zweites Item will,
|
|
# gibt --ref selbst an.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Welche Fassung wird hier gerade geprueft?
|
|
#
|
|
# Das ist die wichtigste Einzelangabe einer Meldung von der Testumgebung, und
|
|
# sie von Hand mitzugeben vergisst jeder. Ohne sie kann der Server nicht
|
|
# entscheiden, ob ein bereits behobener Fehler eine Regression ist oder bloss
|
|
# ein veralteter Pruefstand - und legt im Zweifel ein Regressionsitem an.
|
|
#
|
|
# Quelle ist die manifest.json im Sandkasten, also genau die Fassung, gegen die
|
|
# der Lauf tatsaechlich lief - nicht die, die jemand fuer aktuell haelt.
|
|
detect_build() {
|
|
local manifest
|
|
manifest="$(sandbox_dir)/manifest.json"
|
|
|
|
[ -f "$manifest" ] || return 1
|
|
json_get '.version' < "$manifest" 2>/dev/null || return 1
|
|
}
|
|
|
|
cmd_report() {
|
|
require_project; require_token
|
|
|
|
local type='bug' title='' description='' severity='medium' file='' line=''
|
|
local ref='' error_message='' stack='' build='' target_project='' evidence=''
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--type) type="$2"; shift 2 ;;
|
|
--title) title="$2"; shift 2 ;;
|
|
--desc) description="$2"; shift 2 ;;
|
|
--desc-file) description="$(cat "$2")"; shift 2 ;;
|
|
--severity) severity="$2"; shift 2 ;;
|
|
--file) file="$2"; shift 2 ;;
|
|
--line) line="$2"; shift 2 ;;
|
|
--error) error_message="$2"; shift 2 ;;
|
|
--stack-file) stack="$(cat "$2")"; shift 2 ;;
|
|
--build) build="$2"; shift 2 ;;
|
|
--project) target_project="$2"; shift 2 ;;
|
|
--evidence) evidence="$2"; shift 2 ;;
|
|
--ref) ref="$2"; shift 2 ;;
|
|
*) die "Unbekannter Schalter fuer report: $1" ;;
|
|
esac
|
|
done
|
|
|
|
[ -n "$title" ] || die "--title fehlt."
|
|
|
|
# Der Server kennt genau zwei Typen, und er ist dabei nicht waehlerisch,
|
|
# sondern still: alles, was nicht woertlich "feature_request" heisst, wird
|
|
# zu "bug". Ein "--type feature" landete damit als Fehler im Tracker, und
|
|
# niemand haette es gemerkt. Deshalb hier uebersetzen statt durchreichen -
|
|
# und alles Unbekannte ablehnen, statt es stillschweigend umzudeuten.
|
|
case "$type" in
|
|
bug)
|
|
;;
|
|
feature|feature_request|fr|wunsch|verbesserung)
|
|
type='feature_request' ;;
|
|
idea|idee)
|
|
die "\"idea\" ist kein Typ, sondern ein Schweregrad.
|
|
Gemeint ist vermutlich: --type feature --severity idea" ;;
|
|
*)
|
|
die "Unbekannter Typ \"$type\". Erlaubt sind: bug, feature" ;;
|
|
esac
|
|
|
|
case "$severity" in
|
|
idea|wishlist|low|medium|high|critical) ;;
|
|
*) die "Unbekannter Schweregrad \"$severity\".
|
|
Erlaubt sind: idea, wishlist, low, medium, high, critical" ;;
|
|
esac
|
|
|
|
# Beschreibung darf auch ueber die Standardeingabe kommen - Fehlertexte
|
|
# entstehen meist als Ausgabe eines anderen Befehls.
|
|
if [ -z "$description" ] && [ ! -t 0 ]; then
|
|
description="$(cat)"
|
|
fi
|
|
|
|
[ -n "$description" ] || die "Keine Beschreibung. --desc, --desc-file oder ueber die Standardeingabe."
|
|
|
|
target_project="${target_project:-$DC_PROJECT}"
|
|
|
|
# Fassung nachtragen, wenn sie nicht ausdruecklich angegeben wurde. Nur fuer
|
|
# das eigene Projekt: Bei einer Fremdmeldung ist die eigene Fassung nicht
|
|
# die des gemeldeten Projekts, und eine falsche Angabe waere schlimmer als
|
|
# keine - der Server verglicher sie sonst gegen dessen resolved_in_build.
|
|
if [ -z "$build" ] && [ "$target_project" = "$DC_PROJECT" ]; then
|
|
build="$(detect_build || true)"
|
|
[ -n "$build" ] && dim " geprüfte Fassung: $build (aus manifest.json)"
|
|
fi
|
|
|
|
# Meldung gegen ein FREMDES Projekt verlangt eine Begruendung von
|
|
# mindestens 40 Zeichen - der Server weist sie sonst mit 400
|
|
# missing_evidence ab. Der Grund steht in report.php: Ein Agent in Projekt X
|
|
# kann kaum unterscheiden, ob das fremde Projekt kaputt ist oder ob X es
|
|
# falsch benutzt, und die Voreinstellung eines hilfsbereiten Modells ist im
|
|
# Zweifel "melde es". Hier abzufangen statt den Server ablehnen zu lassen
|
|
# spart einen Fehlversuch und macht die Anforderung sichtbar.
|
|
if [ "$target_project" != "$DC_PROJECT" ]; then
|
|
if [ "${#evidence}" -lt 40 ]; then
|
|
die "Meldung gegen das fremde Projekt \"$target_project\" verlangt --evidence
|
|
mit mindestens 40 Zeichen: Was wurde beobachtet, und warum liegt die Ursache
|
|
dort und nicht in \"$DC_PROJECT\"?"
|
|
fi
|
|
|
|
# Der Server deckelt den Schweregrad einer Fremdmeldung auf medium und
|
|
# sagt es nicht. Wer "critical" schreibt und spaeter "medium" vorfindet,
|
|
# haelt das fuer einen Fehler. Also vorher sagen, dass es so gemeint ist:
|
|
# Die Dringlichkeit in einem fremden Projekt kann der Melder nicht
|
|
# beurteilen - dazu muesste er wissen, wer sonst noch betroffen ist.
|
|
case "$severity" in
|
|
high|critical)
|
|
warn "Schweregrad \"$severity\" wird bei einer Fremdmeldung auf \"medium\" gedeckelt."
|
|
warn "Die Einstufung nimmt \"$target_project\" selbst vor - schreib das Dringliche in die Begruendung."
|
|
;;
|
|
esac
|
|
elif [ -n "$evidence" ]; then
|
|
# Im eigenen Projekt verwirft der Server das Feld ohnehin.
|
|
evidence=''
|
|
fi
|
|
|
|
if [ -z "$ref" ]; then
|
|
ref="testkit-$(printf '%s|%s|%s|%s' "$target_project" "$title" "$file" "$line" \
|
|
| sha256sum | cut -c1-24)"
|
|
fi
|
|
|
|
# Der Testlauf gehoert in die Beschreibung: Ohne die Angabe, auf welcher
|
|
# Maschine und gegen welche Fassung geprueft wurde, ist ein Befund von der
|
|
# Testumgebung nicht von einem aus dem Feld zu unterscheiden.
|
|
description="$description
|
|
|
|
--
|
|
Gemeldet von dc-testkit $DC_TESTKIT_VERSION auf $DC_INSTANCE ($(uname -sr)), Kanal $DC_CHANNEL."
|
|
|
|
local payload
|
|
payload=$(json_object \
|
|
project_slug "$target_project" \
|
|
type "$type" \
|
|
title "$title" \
|
|
description "$description" \
|
|
error_message "$error_message" \
|
|
stack_trace "$stack" \
|
|
severity "$severity" \
|
|
environment 'development' \
|
|
build_version "$build" \
|
|
file_path "$file" \
|
|
line_no "$line" \
|
|
evidence "$evidence" \
|
|
client_ref "$ref")
|
|
|
|
if ! api POST /api/bugtracker/v1/report "$payload"; then
|
|
die "Meldung nicht angenommen: $(api_error)"
|
|
fi
|
|
|
|
# Feldnamen stammen aus public/api/bugtracker/v1/report.php - nicht raten,
|
|
# der Endpunkt heisst sie item_id/is_new und nicht id/duplicate.
|
|
local id is_new hits occurrences
|
|
id=$(printf '%s' "$API_BODY" | json_get '.item_id' || echo '?')
|
|
is_new=$(printf '%s' "$API_BODY" | json_get '.is_new' || echo 'false')
|
|
hits=$(printf '%s' "$API_BODY" | json_get '.idempotent_hit' || echo 'false')
|
|
occurrences=$(printf '%s' "$API_BODY" | json_get '.occurrence_count' || echo '')
|
|
|
|
# Bekannt und in einer spaeteren Fassung behoben. Das ist der haeufigste
|
|
# Fall auf einer Testumgebung, die nicht immer die neueste Fassung faehrt -
|
|
# und die einzige Antwort, bei der NICHTS angelegt wurde.
|
|
local fixed_in reported_build regression
|
|
fixed_in=$(printf '%s' "$API_BODY" | json_get '.known_fixed_in' || echo '')
|
|
reported_build=$(printf '%s' "$API_BODY" | json_get '.reported_build' || echo '')
|
|
regression=$(printf '%s' "$API_BODY" | json_get '.regression_of' || echo '')
|
|
|
|
if [ -n "$fixed_in" ]; then
|
|
ok "Nicht gemeldet - schon behoben."
|
|
dim " Item #$id, behoben in $fixed_in; geprüft wurde $reported_build."
|
|
dim " Es wurde nichts angelegt und nichts hochgezählt."
|
|
dim " Aktualisiere und prüfe erneut. Tritt es dann immer noch auf,"
|
|
dim " ist es eine Regression und gehört gemeldet."
|
|
return 0
|
|
fi
|
|
|
|
# Der Fix, der geprueft werden sollte, traegt nicht. Kein neues Item -
|
|
# Versuch und Fehlschlag bleiben auf einem Strang.
|
|
if [ "$(printf '%s' "$API_BODY" | json_get '.fix_insufficient' || echo false)" = 'true' ]; then
|
|
warn "FIX TRAEGT NICHT: Item #$id steht wieder in der Arbeitsschlange."
|
|
dim " Geprüft wurde $reported_build. Es wurde kein neues Item angelegt;"
|
|
dim " der Beleg hängt am bestehenden."
|
|
return 0
|
|
fi
|
|
|
|
if [ -n "$regression" ]; then
|
|
warn "REGRESSION: Item #$id ist ein Wiederauftreten von #$regression."
|
|
warn "Der Fehler galt als behoben und ist in $reported_build wieder da."
|
|
fi
|
|
|
|
if [ "$is_new" = 'true' ]; then
|
|
ok "Gemeldet als Item #$id in $target_project"
|
|
elif [ "$hits" = 'true' ]; then
|
|
ok "Schon gemeldet - Item #$id, unveraendert (client_ref $ref)"
|
|
else
|
|
ok "Wiederkehrendes Vorkommnis - Item #$id, nun ${occurrences:-?}x gezaehlt"
|
|
fi
|
|
|
|
# Eine Fremdmeldung landet NICHT in "open", sondern im Eingang
|
|
# triage_external. Ohne diesen Hinweis sucht der Meldende sein Item in der
|
|
# offenen Liste des fremden Projekts und findet es nicht - und meldet in
|
|
# drei Wochen dieselbe Sache noch einmal.
|
|
local status affected
|
|
status=$(printf '%s' "$API_BODY" | json_get '.item_status' || echo '')
|
|
affected=$(printf '%s' "$API_BODY" | json_get '.affects' || echo '')
|
|
|
|
if [ "$status" = 'triage_external' ]; then
|
|
dim " Status: triage_external - Eingang fuer Fremdmeldungen."
|
|
dim " \"$target_project\" bestaetigt und stuft ein; vorher zaehlt es"
|
|
dim " nicht in dessen Statistik. Herkunft ist als \"$DC_PROJECT\" vermerkt."
|
|
[ -n "$affected" ] && [ "$affected" != '[]' ] && dim " Bisher betroffen gemeldet: $affected"
|
|
elif [ -n "$status" ]; then
|
|
dim " Status: $status"
|
|
fi
|
|
|
|
dim " $DC_URL/index.php#tab-bugtracker"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Arbeit uebernehmen und abschliessen
|
|
#
|
|
# Duenne Huellen um /manage. Sie sind hier, damit ein Agent den ganzen Kreis
|
|
# ueber ein Werkzeug schliessen kann - uebernehmen, kommentieren, abschliessen -
|
|
# statt fuer die Haelfte auf curl auszuweichen und dabei das Uebernehmen zu
|
|
# vergessen. Wer nicht uebernimmt, arbeitet irgendwann parallel zu jemandem.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Wohin darf ich ueberhaupt melden?
|
|
#
|
|
# Ein Agent, der in seinem Projekt einen Fehler des Deploymentcenters findet,
|
|
# muss dessen Slug kennen. Ihn zu raten geht schief - eine Meldung an ein
|
|
# unbekanntes Projekt wird abgewiesen, und der Agent legt sie dann ersatzweise
|
|
# im eigenen ab, wo sie niemand findet, der sie beheben koennte.
|
|
cmd_projects() {
|
|
require_token
|
|
|
|
if ! api GET /api/bugtracker/v1/projects; then
|
|
die "Projektliste nicht abrufbar: $(api_error)"
|
|
fi
|
|
|
|
printf '%s' "$API_BODY" | python3 -c '
|
|
import json, sys
|
|
|
|
data = json.load(sys.stdin)
|
|
|
|
# Der Endpunkt hat die Liste ueber die Zeit unter verschiedenen Schluesseln
|
|
# gefuehrt; hier wird genommen, was da ist, statt einen Namen zu erzwingen.
|
|
items = None
|
|
for key in ("projects", "items", "data"):
|
|
if isinstance(data.get(key), list):
|
|
items = data[key]
|
|
break
|
|
if items is None:
|
|
items = [v for v in data.values() if isinstance(v, list)][:1]
|
|
items = items[0] if items else []
|
|
|
|
if not items:
|
|
print(" (keine Projekte gemeldet)")
|
|
raise SystemExit(0)
|
|
|
|
for entry in items:
|
|
if isinstance(entry, str):
|
|
print(" %s" % entry)
|
|
continue
|
|
slug = entry.get("slug") or entry.get("project_slug") or "?"
|
|
name = entry.get("name") or ""
|
|
print(" %-24s %s" % (slug, name))
|
|
'
|
|
|
|
dim ""
|
|
dim "Eigenes Projekt: $DC_PROJECT"
|
|
dim "In ein fremdes melden: --project <slug> --evidence \"...\" (mind. 40 Zeichen)"
|
|
}
|
|
|
|
cmd_next() {
|
|
require_project; require_token
|
|
|
|
local payload
|
|
payload=$(json_object project_slug "$DC_PROJECT" limit "${1:-1}")
|
|
|
|
if ! api POST '/api/bugtracker/v1/manage?action=next' "$payload"; then
|
|
if printf '%s' "$API_BODY" | grep -q 'already_claimed'; then
|
|
warn "Schon von jemand anderem uebernommen - naechstes Item nehmen."
|
|
return 2
|
|
fi
|
|
die "Kein Item erhalten: $(api_error)"
|
|
fi
|
|
|
|
# Eine Pruefaufgabe sieht im JSON aus wie jede andere Arbeit, ist aber eine
|
|
# andere Frage: nicht "behebe das", sondern "traegt der Fix?". Ohne diesen
|
|
# Hinweis faengt ein Agent an, einen bereits behobenen Fehler noch einmal zu
|
|
# beheben.
|
|
printf '%s' "$API_BODY" | python3 -c '
|
|
import json, sys
|
|
|
|
try:
|
|
data = json.load(sys.stdin)
|
|
except Exception:
|
|
sys.exit(0)
|
|
|
|
for item in data.get("items") or []:
|
|
if item.get("status") != "in_verification":
|
|
continue
|
|
|
|
print("")
|
|
print(" NACHPRUEFUNG, keine neue Fehlersuche:")
|
|
print(" Item #%s %s" % (item.get("id"), item.get("title")))
|
|
print(" Behoben laut Tracker in: %s" % (item.get("resolved_in_build") or "unbekannt"))
|
|
if item.get("project_slug"):
|
|
print(" Projekt: %s" % item["project_slug"])
|
|
print("")
|
|
print(" Gegen diesen Build testen. Dann eines von beidem:")
|
|
print(" traegt -> dc-testkit confirm %s \"kurz was geprueft wurde\"" % item.get("id"))
|
|
print(" traegt nicht-> den Fehler erneut melden (dc-testkit report ...);")
|
|
print(" das Item wechselt dann selbst auf fix_insufficient.")
|
|
' || true
|
|
|
|
printf '%s\n' "$API_BODY"
|
|
}
|
|
|
|
# Nachpruefung bestaetigen.
|
|
#
|
|
# Bewusst getrennt von "resolve": resolve behauptet "ich habe es behoben, in
|
|
# Build X" und setzt resolved_in_build. Hier wird nichts behauptet, sondern
|
|
# etwas bestaetigt - der Fix eines anderen. Beides in einen Befehl zu legen
|
|
# hiesse, den Unterschied zwischen Fixen und Pruefen einzuebnen.
|
|
cmd_confirm() {
|
|
require_token
|
|
[ $# -ge 1 ] || die "Aufruf: dc-testkit confirm <id> [was geprueft wurde]"
|
|
|
|
local id="$1"; shift
|
|
local note="${1:-}"
|
|
local build
|
|
|
|
build="$(detect_build || true)"
|
|
|
|
# Eigene Aktion, nicht "status=closed" mit schmueckendem Kommentar.
|
|
#
|
|
# Bis 2.7.1 war es genau das - und damit war eine bestaetigte Nachpruefung
|
|
# im Datenbestand nicht von "jemand hat das Item zugemacht" zu
|
|
# unterscheiden. Die eine Frage, fuer die das Verfahren gebaut ist ("wurde
|
|
# das gegen ein ausgeliefertes Paket geprueft?"), war nicht beantwortbar,
|
|
# weil die Antwort in einem Kommentartext stand.
|
|
#
|
|
# Die gepruefte Fassung geht deshalb als Feld mit, nicht im Fliesstext:
|
|
# Der Server haelt sie in verified_in_build und kann sie gegen
|
|
# resolved_in_build stellen. Gehen die beiden auseinander, wurde gegen
|
|
# etwas anderes geprueft als angekuendigt - und das sieht man dann.
|
|
local payload
|
|
payload=$(json_object verified_in_build "${build:-}" notes "$note")
|
|
|
|
if ! api POST "/api/bugtracker/v1/manage?action=confirm&id=$id" "$payload"; then
|
|
case "$API_STATUS" in
|
|
404)
|
|
# Ein Server vor 2.8.0 kennt die Aktion nicht. Der alte Weg
|
|
# bleibt richtig - er sagt nur weniger.
|
|
warn "Dieser Server kennt 'confirm' noch nicht - alter Weg (status=closed)."
|
|
warn " Die gepruefte Fassung landet dann nur im Kommentar."
|
|
|
|
local text="Nachpruefung bestanden${build:+ (geprueft gegen $build)}."
|
|
[ -n "$note" ] && text="$text $note"
|
|
|
|
api POST "/api/bugtracker/v1/manage?action=status&id=$id" \
|
|
"$(json_object status closed notes "$text")" \
|
|
|| die "Bestaetigung nicht angenommen: $(api_error)"
|
|
;;
|
|
409)
|
|
# Kein Fehler des Aufrufers, sondern eine Aussage ueber das
|
|
# Item: Da ist nichts, was sich bestaetigen liesse.
|
|
die "Nichts zu bestaetigen: $(api_error)"
|
|
;;
|
|
*)
|
|
die "Bestaetigung nicht angenommen: $(api_error)"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
ok "Item #$id bestaetigt und geschlossen${build:+ - geprueft gegen $build}"
|
|
}
|
|
|
|
cmd_comment() {
|
|
require_token
|
|
[ $# -ge 2 ] || die "Aufruf: dc-testkit comment <id> <text> [aktion]"
|
|
|
|
local id="$1"; shift
|
|
local comment="$1"; shift
|
|
local action="${1:-investigated}"
|
|
|
|
local payload
|
|
payload=$(json_object comment "$comment" action_taken "$action")
|
|
|
|
api POST "/api/bugtracker/v1/manage?action=comment&id=$id" "$payload" \
|
|
|| die "Kommentar nicht angenommen: $(api_error)"
|
|
|
|
ok "Kommentar an Item #$id"
|
|
}
|
|
|
|
cmd_resolve() {
|
|
require_token
|
|
[ $# -ge 2 ] || die "Aufruf: dc-testkit resolve <id> <build> [notizen]"
|
|
|
|
local id="$1"; shift
|
|
local build="$1"; shift
|
|
local notes="${1:-}"
|
|
|
|
local payload
|
|
payload=$(json_object resolved_in_build "$build" resolution_notes "$notes")
|
|
|
|
api POST "/api/bugtracker/v1/manage?action=resolve&id=$id" "$payload" \
|
|
|| die "Abschluss nicht angenommen: $(api_error)"
|
|
|
|
ok "Item #$id abgeschlossen (behoben in $build)"
|
|
}
|
|
|
|
cmd_release_claim() {
|
|
require_token
|
|
[ $# -ge 1 ] || die "Aufruf: dc-testkit unclaim <id> [grund]"
|
|
|
|
local id="$1"; shift
|
|
local payload
|
|
payload=$(json_object note "${1:-Zurueckgegeben durch dc-testkit}")
|
|
|
|
api POST "/api/bugtracker/v1/manage?action=release&id=$id" "$payload" \
|
|
|| die "Rueckgabe nicht angenommen: $(api_error)"
|
|
|
|
ok "Item #$id zurueckgegeben"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# run - der ganze Durchlauf
|
|
#
|
|
# Lizenz holen, Agent besorgen, in einen leeren Sandkasten ausliefern, Ergebnis
|
|
# pruefen. Scheitert ein Schritt, wird der Befund gemeldet statt nur gedruckt -
|
|
# denn ein fehlgeschlagener Testlauf, von dem nur das Terminal weiss, ist kein
|
|
# Testlauf gewesen.
|
|
#
|
|
# Die Meldung ist ueber client_ref entstoert: Derselbe Fehlschlag in fuenf
|
|
# Naechten erzeugt ein Item, nicht fuenf.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
cmd_run() {
|
|
require_project; require_token
|
|
|
|
local report=1
|
|
[ "${1:-}" = '--no-report' ] && report=0
|
|
|
|
local log
|
|
log="$(state_dir)/last-run.log"
|
|
ensure_state
|
|
|
|
local failed_step=''
|
|
|
|
# Der Schritt laeuft in einer SUBSHELL, und das ist der Punkt: die() beendet
|
|
# mit exit. Ohne Subshell risse der erste Fehlschlag das ganze Skript mit -
|
|
# und ausgerechnet der Durchlauf, der Fehlschlaege melden soll, kaeme nie
|
|
# bis zur Meldung. Dateien, die ein Schritt anlegt (Schluessel, Binary,
|
|
# Sandkasten), ueberleben die Subshell ohnehin; nur Variablen nicht, und die
|
|
# gibt niemand weiter.
|
|
_stage() {
|
|
local name="$1"; shift
|
|
step "$name"
|
|
|
|
if ( "$@" ) >>"$log" 2>&1; then
|
|
ok "$name"
|
|
return 0
|
|
fi
|
|
|
|
failed_step="$name"
|
|
warn "$name fehlgeschlagen"
|
|
return 1
|
|
}
|
|
|
|
: > "$log"
|
|
|
|
{
|
|
_stage "DEV-Lizenz holen" cmd_license \
|
|
&& _stage "update-agent besorgen" cmd_agent \
|
|
&& _stage "Erstauslieferung" cmd_install \
|
|
&& _stage "Ergebnis pruefen" cmd_verify \
|
|
&& _stage "Update auf latest" cmd_update \
|
|
&& _stage "Ergebnis erneut pruefen" cmd_verify
|
|
} || true
|
|
|
|
if [ -z "$failed_step" ]; then
|
|
printf '\n%sDurchlauf vollstaendig.%s\n' "$C_GREEN" "$C_RESET"
|
|
dim "Protokoll: $log"
|
|
return 0
|
|
fi
|
|
|
|
printf '\n%sDurchlauf abgebrochen bei: %s%s\n' "$C_RED" "$failed_step" "$C_RESET"
|
|
dim "Protokoll: $log"
|
|
|
|
if [ "$report" -eq 1 ]; then
|
|
step "Befund melden"
|
|
tail -c 4000 "$log" | cmd_report \
|
|
--type bug \
|
|
--title "Testlauf scheitert: $failed_step ($DC_PROJECT/$DC_CHANNEL)" \
|
|
--severity high \
|
|
--error "$(tail -n 1 "$log")" \
|
|
--ref "testkit-run-$(printf '%s|%s|%s' "$DC_PROJECT" "$DC_CHANNEL" "$failed_step" | sha256sum | cut -c1-24)"
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# cleanup - nichts zuruecklassen
|
|
#
|
|
# Die Regel der Testumgebung lautet: Nichts Dauerhaftes hinterlassen, was nicht
|
|
# in der Belegungsliste steht. Der DEV-Schluessel gehoert dazu - er laeuft zwar
|
|
# von selbst ab, aber ein zurueckgegebener Schluessel macht sofort Platz fuer
|
|
# die naechste Maschine.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
cmd_cleanup() {
|
|
require_project
|
|
|
|
local only_sandbox=0
|
|
[ "${1:-}" = '--sandbox' ] && only_sandbox=1
|
|
|
|
local dir
|
|
dir="$(sandbox_dir)"
|
|
|
|
if [ -d "$dir" ]; then
|
|
rm -rf "$dir"
|
|
ok "Sandkasten entfernt: $dir"
|
|
fi
|
|
|
|
[ "$only_sandbox" -eq 1 ] && return 0
|
|
|
|
if [ -f "$(key_file)" ] && [ -n "$DC_TOKEN" ]; then
|
|
cmd_license --revoke || warn "DEV-Schluessel liess sich nicht zurueckgeben - er laeuft von selbst ab."
|
|
fi
|
|
|
|
rm -f "$(agent_bin)" "$(state_dir)/last-run.log"
|
|
ok "Aufgeraeumt."
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Einstieg
|
|
# ---------------------------------------------------------------------------
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
dc-testkit $DC_TESTKIT_VERSION - Testlaeufe gegen das Deploymentcenter
|
|
|
|
dc-testkit [--project <slug>] [--channel <kanal>] [--url <adresse>] <befehl>
|
|
|
|
Einrichten und pruefen
|
|
doctor Werkzeuge, Netz, eigener Stand, Token und Projekt
|
|
selfupdate [--pruefen] Dieses Skript auf die ausgelieferte Fassung bringen
|
|
license [--ttl N] [--print] [--revoke]
|
|
DEV-Lizenzschluessel holen, verlaengern, zurueckgeben
|
|
agent [--force] update-agent herunterladen, Pruefsumme abgleichen
|
|
|
|
Auslieferung pruefen
|
|
check Gibt es ein Update?
|
|
install Erstauslieferung in einen leeren Sandkasten
|
|
update [--version <ver>] Update oder Rueckfall auf eine aeltere Fassung
|
|
repair Integritaetsreparatur
|
|
list Verfuegbare Fassungen
|
|
verify Was liegt im Sandkasten, und passt es zum Server?
|
|
run [--no-report] Alles nacheinander; meldet, was scheitert
|
|
|
|
Bugtracker
|
|
projects Wohin darf ich melden? Alle Projekt-Slugs
|
|
report --title T --desc D [--type bug|feature] [--severity S]
|
|
[--file F --line N] [--error M] [--stack-file P]
|
|
report --project FREMDER-SLUG --evidence "warum dort und nicht hier"
|
|
Befund im FREMDEN Projekt melden. Begruendung mind. 40 Zeichen,
|
|
Schweregrad wird auf medium gedeckelt, landet in triage_external.
|
|
next [limit] Naechstes Item uebernehmen - auch Nachpruefungen
|
|
comment <id> <text> [aktion]
|
|
resolve <id> <build> [notizen] "ich habe es behoben"
|
|
confirm <id> [notiz] "der Fix eines anderen traegt" -> schliesst
|
|
unclaim <id> [grund] Uebernahme zurueckgeben
|
|
|
|
Oberflaeche pruefen (braucht DC_DISPLAY)
|
|
display [status|start|stop] Eigenes virtuelles Display
|
|
shot <name> Aufnahme nach /tmp/<projekt>_<name>.png
|
|
|
|
Aufraeumen
|
|
cleanup [--sandbox] Sandkasten weg, Schluessel zurueckgeben
|
|
|
|
Schweregrade: idea, wishlist, low, medium, high, critical
|
|
Typen: bug, feature ("idea" ist ein Schweregrad, kein Typ)
|
|
|
|
Konfiguration: $CONFIG_FILE
|
|
DC_URL, DC_TOKEN, DC_PROJECT, DC_CHANNEL, DC_INSTANCE, DC_DISPLAY
|
|
DC_PROJECT_DIR - Projekt-Repo; ohne DC_TOKEN wird es aus dessen
|
|
.mcp.json gelesen (env.DC_TOKEN)
|
|
EOF
|
|
}
|
|
|
|
main() {
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--project) DC_PROJECT="$2"; shift 2 ;;
|
|
--channel) DC_CHANNEL="$2"; shift 2 ;;
|
|
--url) DC_URL="${2%/}"; shift 2 ;;
|
|
--instance) DC_INSTANCE="$2"; shift 2 ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
--version) printf 'dc-testkit %s\n' "$DC_TESTKIT_VERSION"; exit 0 ;;
|
|
*) break ;;
|
|
esac
|
|
done
|
|
|
|
local command="${1:-}"
|
|
[ $# -gt 0 ] && shift
|
|
|
|
token_aufloesen
|
|
|
|
case "$command" in
|
|
doctor) cmd_doctor "$@" ;;
|
|
selfupdate) cmd_selfupdate "$@" ;;
|
|
display) cmd_display "$@" ;;
|
|
shot) cmd_shot "$@" ;;
|
|
license) cmd_license "$@" ;;
|
|
agent) cmd_agent "$@" ;;
|
|
check) cmd_check "$@" ;;
|
|
install) cmd_install "$@" ;;
|
|
update) cmd_update "$@" ;;
|
|
repair) cmd_repair "$@" ;;
|
|
list) cmd_list "$@" ;;
|
|
verify) cmd_verify "$@" ;;
|
|
run) cmd_run "$@" ;;
|
|
projects) cmd_projects "$@" ;;
|
|
report) cmd_report "$@" ;;
|
|
next) cmd_next "$@" ;;
|
|
comment) cmd_comment "$@" ;;
|
|
resolve) cmd_resolve "$@" ;;
|
|
confirm) cmd_confirm "$@" ;;
|
|
unclaim) cmd_release_claim "$@" ;;
|
|
cleanup) cmd_cleanup "$@" ;;
|
|
''|help) usage ;;
|
|
*) die "Unbekannter Befehl: $command (dc-testkit --help)" ;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|