Publication atomique du catalogue par lien symbolique, droits du volume, repli sur le catalogue de l'image
build / Garde-fou (pull_request) Successful in 11s
build / Images Harbor (catalog-sync, Dockerfile.catalog-sync) (pull_request) Skipped
build / Images Harbor (web, Dockerfile) (pull_request) Skipped
build / Images Harbor (catalog-sync, Dockerfile.catalog-sync) (push) Successful in 9m49s
build / Garde-fou (push) Successful in 10s
build / Images Harbor (web, Dockerfile) (push) Successful in 11m25s
build / Garde-fou (pull_request) Successful in 11s
build / Images Harbor (catalog-sync, Dockerfile.catalog-sync) (pull_request) Skipped
build / Images Harbor (web, Dockerfile) (pull_request) Skipped
build / Images Harbor (catalog-sync, Dockerfile.catalog-sync) (push) Successful in 9m49s
build / Garde-fou (push) Successful in 10s
build / Images Harbor (web, Dockerfile) (push) Successful in 11m25s
This commit is contained in:
1 parent
c51a6379bd
commit
5f76b08ae6
9 files changed
+126
-38
No files matched your search
+2
-1
@@ -11,10 +11,11 @@ TZ=Europe/Paris
|
||||
GUNICORN_WORKERS=2
|
||||
|
||||
# Catalogue synchronise depuis Git (laisser vide pour n'utiliser que celui de l'image)
|
||||
CATALOG_REPO=https://gitea.tips-of-mine.com/hubert/enclume-catalogue.git
|
||||
CATALOG_REPO=https://gitea.tips-of-mine.com/Tips-Of-Mine/Enclume.git
|
||||
CATALOG_BRANCH=main
|
||||
CATALOG_SUBDIR=catalog
|
||||
SYNC_INTERVAL=300
|
||||
CATALOG_SUBDIR=catalog
|
||||
|
||||
# Base de donnees (chantier 2)
|
||||
POSTGRES_PASSWORD=a-remplacer
|
||||
@@ -4,19 +4,21 @@
|
||||
# image de l'application.
|
||||
FROM alpine:3.20
|
||||
|
||||
RUN apk add --no-cache git curl tini \
|
||||
RUN apk add --no-cache git curl tini su-exec \
|
||||
&& addgroup -g 10001 enclume \
|
||||
&& adduser -u 10001 -G enclume -D -H enclume
|
||||
|
||||
COPY docker/catalog-sync.sh /usr/local/bin/catalog-sync
|
||||
RUN chmod +x /usr/local/bin/catalog-sync
|
||||
COPY docker/catalog-sync-entrypoint.sh /usr/local/bin/entrypoint
|
||||
RUN chmod +x /usr/local/bin/catalog-sync /usr/local/bin/entrypoint
|
||||
|
||||
ENV CATALOG_REPO="" \
|
||||
CATALOG_BRANCH=main \
|
||||
CATALOG_SUBDIR=catalog \
|
||||
CATALOG_DEST=/catalogue \
|
||||
SYNC_INTERVAL=300 \
|
||||
REVISIONS_CONSERVEES=3 \
|
||||
RELOAD_URL=http://web:8080/recharger
|
||||
|
||||
USER enclume
|
||||
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/catalog-sync"]
|
||||
# Demarre en root pour ajuster le volume, puis bascule sur l'utilisateur enclume.
|
||||
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint"]
|
||||
+18
-8
@@ -13,12 +13,14 @@ services:
|
||||
image: ${ENCLUME_REGISTRY:-registry.tips-of-mine.com}/enclume/web:${ENCLUME_TAG:?definir ENCLUME_TAG dans .env}
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ENCLUME_CATALOG: /catalogue
|
||||
# catalog-sync publie chaque revision a cote et fait basculer ce lien.
|
||||
# Si le volume est vide, l'application se rabat sur le catalogue de l'image.
|
||||
ENCLUME_CATALOG: /catalogue/actuel
|
||||
GUNICORN_WORKERS: ${GUNICORN_WORKERS:-2}
|
||||
TZ: ${TZ:-Europe/Paris}
|
||||
volumes:
|
||||
- catalogue:/catalogue:ro
|
||||
networks: [interne, traefik]
|
||||
networks: [back_network_enclume, db_network_enclume, traefik_front_network]
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:size=32m,mode=1777
|
||||
@@ -32,7 +34,7 @@ services:
|
||||
options: {max-size: "10m", max-file: "3"}
|
||||
labels:
|
||||
traefik.enable: "true"
|
||||
traefik.docker.network: traefik
|
||||
traefik.docker.network: traefik_front_network
|
||||
traefik.http.routers.enclume.rule: Host(`${ENCLUME_DOMAINE:?definir ENCLUME_DOMAINE}`)
|
||||
traefik.http.routers.enclume.entrypoints: websecure
|
||||
traefik.http.routers.enclume.tls.certresolver: ${TRAEFIK_RESOLVER:-cloudflare}
|
||||
@@ -57,7 +59,7 @@ services:
|
||||
RELOAD_URL: http://web:8080/recharger
|
||||
volumes:
|
||||
- catalogue:/catalogue
|
||||
networks: [interne]
|
||||
networks: [back_network_enclume]
|
||||
security_opt: ["no-new-privileges:true"]
|
||||
deploy:
|
||||
resources:
|
||||
@@ -78,7 +80,7 @@ services:
|
||||
TZ: ${TZ:-Europe/Paris}
|
||||
volumes:
|
||||
- donnees:/var/lib/postgresql/data
|
||||
networks: [interne]
|
||||
networks: [db_network_enclume]
|
||||
security_opt: ["no-new-privileges:true"]
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U enclume -d enclume"]
|
||||
@@ -94,7 +96,15 @@ volumes:
|
||||
donnees:
|
||||
|
||||
networks:
|
||||
interne:
|
||||
internal: true
|
||||
traefik:
|
||||
# Reseau du reverse proxy existant.
|
||||
traefik_front_network:
|
||||
external: true
|
||||
|
||||
# catalog-sync doit joindre Gitea : ce reseau a donc un acces sortant.
|
||||
back_network_enclume:
|
||||
driver: bridge
|
||||
attachable: true
|
||||
|
||||
# La base n'a aucune raison de sortir : elle reste isolee avec l'application.
|
||||
db_network_enclume:
|
||||
internal: true
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Le volume partage appartient a root a sa creation. On l'ajuste en root, puis
|
||||
# on abandonne les privileges avant de lancer la boucle de synchronisation.
|
||||
set -eu
|
||||
|
||||
: "${CATALOG_DEST:=/catalogue}"
|
||||
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
mkdir -p "$CATALOG_DEST"
|
||||
chown enclume:enclume "$CATALOG_DEST"
|
||||
exec su-exec enclume:enclume /usr/local/bin/catalog-sync "$@"
|
||||
fi
|
||||
|
||||
exec /usr/local/bin/catalog-sync "$@"
|
||||
+48
-20
@@ -1,42 +1,70 @@
|
||||
#!/bin/sh
|
||||
# Clone ou met a jour le depot du catalogue, publie son contenu dans le volume
|
||||
# partage, puis notifie l'application. Boucle jusqu'a l'arret du conteneur.
|
||||
#
|
||||
# La publication passe par un lien symbolique : on ne peut pas renommer le
|
||||
# point de montage lui-meme, mais on peut faire basculer un lien a l'interieur,
|
||||
# et cette bascule est atomique. L'application lit CATALOG_DEST/actuel.
|
||||
set -eu
|
||||
|
||||
if [ -z "${CATALOG_REPO}" ]; then
|
||||
echo "catalog-sync : CATALOG_REPO non defini, rien a synchroniser." >&2
|
||||
exit 0
|
||||
: "${CATALOG_DEST:=/catalogue}"
|
||||
: "${CATALOG_BRANCH:=main}"
|
||||
: "${CATALOG_SUBDIR:=catalog}"
|
||||
: "${SYNC_INTERVAL:=300}"
|
||||
: "${REVISIONS_CONSERVEES:=3}"
|
||||
|
||||
if [ -z "${CATALOG_REPO:-}" ]; then
|
||||
echo "catalog-sync : CATALOG_REPO non defini. L'application utilisera le catalogue de son image."
|
||||
# On reste en vie sans rien faire : le conteneur ne doit pas boucler en redemarrage.
|
||||
while true; do sleep 3600; done
|
||||
fi
|
||||
|
||||
TRAVAIL=/tmp/depot
|
||||
|
||||
sync_une_fois() {
|
||||
if [ -d "$TRAVAIL/.git" ]; then
|
||||
git -C "$TRAVAIL" fetch --quiet --depth 1 origin "$CATALOG_BRANCH"
|
||||
git -C "$TRAVAIL" reset --quiet --hard "origin/$CATALOG_BRANCH"
|
||||
git -C "$TRAVAIL" fetch --quiet --depth 1 origin "$CATALOG_BRANCH" || return 1
|
||||
git -C "$TRAVAIL" reset --quiet --hard "origin/$CATALOG_BRANCH" || return 1
|
||||
else
|
||||
git clone --quiet --depth 1 --branch "$CATALOG_BRANCH" "$CATALOG_REPO" "$TRAVAIL"
|
||||
rm -rf "$TRAVAIL"
|
||||
git clone --quiet --depth 1 --branch "$CATALOG_BRANCH" "$CATALOG_REPO" "$TRAVAIL" || return 1
|
||||
fi
|
||||
|
||||
SOURCE="$TRAVAIL/$CATALOG_SUBDIR"
|
||||
[ -d "$SOURCE" ] || { echo "catalog-sync : $CATALOG_SUBDIR absent du depot." >&2; return 1; }
|
||||
if [ ! -d "$SOURCE" ]; then
|
||||
echo "catalog-sync : le repertoire $CATALOG_SUBDIR est absent du depot." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Publication atomique : on ecrit a cote, puis on bascule.
|
||||
NEUF="${CATALOG_DEST}.neuf"
|
||||
rm -rf "$NEUF"
|
||||
mkdir -p "$NEUF"
|
||||
cp -a "$SOURCE"/. "$NEUF"/
|
||||
rm -rf "${CATALOG_DEST}.ancien"
|
||||
[ -d "$CATALOG_DEST" ] && mv "$CATALOG_DEST" "${CATALOG_DEST}.ancien"
|
||||
mv "$NEUF" "$CATALOG_DEST"
|
||||
rm -rf "${CATALOG_DEST}.ancien"
|
||||
REVISION=$(git -C "$TRAVAIL" rev-parse --short HEAD) || return 1
|
||||
CIBLE="$CATALOG_DEST/rev-$REVISION"
|
||||
|
||||
REVISION=$(git -C "$TRAVAIL" rev-parse --short HEAD)
|
||||
echo "catalog-sync : catalogue a jour ($REVISION)"
|
||||
curl -fsS -X POST "$RELOAD_URL" >/dev/null 2>&1 || echo "catalog-sync : rechargement non confirme"
|
||||
if [ -L "$CATALOG_DEST/actuel" ] && [ "$(readlink "$CATALOG_DEST/actuel")" = "$CIBLE" ]; then
|
||||
return 0 # deja publie, rien a faire
|
||||
fi
|
||||
|
||||
rm -rf "$CIBLE.partiel" "$CIBLE"
|
||||
mkdir -p "$CIBLE.partiel"
|
||||
cp -a "$SOURCE"/. "$CIBLE.partiel"/ || return 1
|
||||
mv "$CIBLE.partiel" "$CIBLE" || return 1
|
||||
|
||||
# Bascule atomique du lien, puis menage des anciennes revisions.
|
||||
ln -sfn "$CIBLE" "$CATALOG_DEST/actuel.neuf"
|
||||
mv -f "$CATALOG_DEST/actuel.neuf" "$CATALOG_DEST/actuel" || return 1
|
||||
|
||||
ls -1dt "$CATALOG_DEST"/rev-* 2>/dev/null | tail -n +$((REVISIONS_CONSERVEES + 1)) | while read -r vieux; do
|
||||
rm -rf "$vieux"
|
||||
done
|
||||
|
||||
echo "catalog-sync : catalogue publie ($REVISION)"
|
||||
if [ -n "${RELOAD_URL:-}" ]; then
|
||||
curl -fsS -X POST "$RELOAD_URL" >/dev/null 2>&1 || echo "catalog-sync : rechargement non confirme"
|
||||
fi
|
||||
}
|
||||
|
||||
while true; do
|
||||
sync_une_fois || echo "catalog-sync : echec de la synchronisation, nouvelle tentative dans ${SYNC_INTERVAL}s" >&2
|
||||
if ! sync_une_fois; then
|
||||
echo "catalog-sync : echec de la synchronisation, nouvelle tentative dans ${SYNC_INTERVAL}s" >&2
|
||||
fi
|
||||
sleep "$SYNC_INTERVAL"
|
||||
done
|
||||
@@ -71,7 +71,7 @@ déploiement urgent. Le rapport Trivy dans la CI suffit à surveiller.
|
||||
## Enchaînement
|
||||
|
||||
```bash
|
||||
git remote add origin https://gitea.tips-of-mine.com/hubert/enclume.git
|
||||
git remote add origin https://gitea.tips-of-mine.com/Tips-Of-Mine/Enclume.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<footer class="pied">
|
||||
<span>Projet indépendant, sans lien avec Centreon SAS. Centreon est une marque déposée de son propriétaire.</span>
|
||||
<span class="grow"></span>
|
||||
<a href="https://gitea.tips-of-mine.com/hubert/enclume">Code source</a>
|
||||
<a href="https://gitea.tips-of-mine.com/Tips-Of-Mine/Enclume">Code source</a>
|
||||
<a href="/sante">État du service</a>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
+21
-3
@@ -22,13 +22,30 @@ from .packs import pack_to_project
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
CATALOG_DIR = os.environ.get("ENCLUME_CATALOG", os.path.join(BASE_DIR, "catalog"))
|
||||
CATALOG_FALLBACK = os.environ.get("ENCLUME_CATALOG_FALLBACK", os.path.join(BASE_DIR, "catalog"))
|
||||
MAX_BODY = 2 * 1024 * 1024 # un projet honnete pese quelques dizaines de ko
|
||||
|
||||
|
||||
def _repertoire_catalogue() -> str:
|
||||
"""Repertoire a lire : celui synchronise, sinon celui livre dans l'image.
|
||||
|
||||
Le volume partage est vide tant que catalog-sync n'a rien publie, et il peut
|
||||
le rester si Git est injoignable. Servir un site sans aucun pack serait pire
|
||||
que servir un catalogue un peu ancien : on se rabat sur celui de l'image.
|
||||
"""
|
||||
for chemin in (CATALOG_DIR, CATALOG_FALLBACK):
|
||||
if not chemin or not os.path.isdir(chemin):
|
||||
continue
|
||||
for _racine, _dossiers, fichiers in os.walk(chemin):
|
||||
if any(f.endswith((".yml", ".yaml")) for f in fichiers):
|
||||
return chemin
|
||||
return CATALOG_FALLBACK
|
||||
|
||||
|
||||
def create_app() -> Flask:
|
||||
app = Flask(__name__)
|
||||
app.config["MAX_CONTENT_LENGTH"] = MAX_BODY
|
||||
app.config["CATALOG"] = Catalog(CATALOG_DIR)
|
||||
app.config["CATALOG"] = Catalog(_repertoire_catalogue())
|
||||
|
||||
def catalog() -> Catalog:
|
||||
return app.config["CATALOG"]
|
||||
@@ -76,6 +93,7 @@ def create_app() -> Flask:
|
||||
"version": os.environ.get("ENCLUME_VERSION", "dev"),
|
||||
"revision": os.environ.get("ENCLUME_COMMIT", "inconnu"),
|
||||
"packs": len(cat.packs),
|
||||
"source_catalogue": cat.root,
|
||||
"erreurs_catalogue": cat.errors,
|
||||
}
|
||||
)
|
||||
@@ -156,8 +174,8 @@ def create_app() -> Flask:
|
||||
|
||||
@app.post("/recharger")
|
||||
def reload_catalog() -> Response:
|
||||
app.config["CATALOG"] = Catalog(CATALOG_DIR)
|
||||
app.config["CATALOG"] = Catalog(_repertoire_catalogue())
|
||||
cat = catalog()
|
||||
return jsonify({"packs": len(cat.packs), "erreurs": cat.errors})
|
||||
return jsonify({"packs": len(cat.packs), "source": cat.root, "erreurs": cat.errors})
|
||||
|
||||
return app
|
||||
@@ -76,3 +76,18 @@ def test_api_de_generation():
|
||||
def test_sante():
|
||||
reponse = create_app().test_client().get("/sante")
|
||||
assert reponse.get_json()["statut"] == "ok"
|
||||
|
||||
|
||||
def test_repli_sur_le_catalogue_de_l_image(tmp_path, monkeypatch):
|
||||
"""Un volume vide ne doit jamais donner un site sans aucun pack."""
|
||||
import importlib
|
||||
|
||||
monkeypatch.setenv("ENCLUME_CATALOG", str(tmp_path))
|
||||
monkeypatch.setenv("ENCLUME_CATALOG_FALLBACK", "catalog")
|
||||
import enclume.webapp as webapp
|
||||
|
||||
importlib.reload(webapp)
|
||||
reponse = webapp.create_app().test_client().get("/sante")
|
||||
donnees = reponse.get_json()
|
||||
assert donnees["packs"] >= 20
|
||||
assert donnees["source_catalogue"] == "catalog"
|
||||
Reference in new issue
Block a user