Merge pull request #36 from R0m1k3/claude/combohr-app-recreation-plan-yu7wre
Réparer l'intégration continue, et poser des ports internes non communs
This commit is contained in:
6 files changed
+70
-19
No files matched your search
+6
-2
@@ -71,7 +71,11 @@ RUN chmod +x ./docker/entrypoint.sh
|
|||||||
RUN mkdir -p /data/documents /secrets && chown -R nextjs:nodejs /data /secrets
|
RUN mkdir -p /data/documents /secrets && chown -R nextjs:nodejs /data /secrets
|
||||||
|
|
||||||
USER nextjs
|
USER nextjs
|
||||||
EXPOSE 3000
|
# Port peu courant jusque **dans** l'image : le reverse-proxy porte alors la
|
||||||
ENV PORT=3000 HOSTNAME=0.0.0.0
|
# même valeur partout, et rien ne rappelle un 3000 par défaut. Surchargeable
|
||||||
|
# par la pile, qui aligne publication, sonde de santé et serveur sur une
|
||||||
|
# unique variable.
|
||||||
|
EXPOSE 9317
|
||||||
|
ENV PORT=9317 HOSTNAME=0.0.0.0
|
||||||
|
|
||||||
CMD ["./docker/entrypoint.sh"]
|
CMD ["./docker/entrypoint.sh"]
|
||||||
+28
-12
@@ -12,6 +12,11 @@ services:
|
|||||||
# du même réseau. Le changer reste recommandé.
|
# du même réseau. Le changer reste recommandé.
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-planflow-interne}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-planflow-interne}
|
||||||
POSTGRES_DB: ${POSTGRES_DB:-planflow}
|
POSTGRES_DB: ${POSTGRES_DB:-planflow}
|
||||||
|
# Port d'écoute **dans** le conteneur. Un port non commun ne protège de
|
||||||
|
# rien — sur un réseau Docker, chaque conteneur a son adresse et deux
|
||||||
|
# services peuvent écouter le même port sans se gêner — mais il lève
|
||||||
|
# l'ambiguïté quand plusieurs bases cohabitent derrière le même proxy.
|
||||||
|
PGPORT: ${POSTGRES_PORT_INTERNAL:-5439}
|
||||||
# Transmis au script d'initialisation, qui crée le rôle de connexion de
|
# Transmis au script d'initialisation, qui crée le rôle de connexion de
|
||||||
# l'application — celui-ci ne doit surtout pas être le superutilisateur.
|
# l'application — celui-ci ne doit surtout pas être le superutilisateur.
|
||||||
APP_DB_USER: ${APP_DB_USER:-planflow_app}
|
APP_DB_USER: ${APP_DB_USER:-planflow_app}
|
||||||
@@ -53,13 +58,17 @@ services:
|
|||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
) &
|
) &
|
||||||
exec docker-entrypoint.sh postgres
|
exec docker-entrypoint.sh postgres -p "$$PGPORT"
|
||||||
volumes:
|
volumes:
|
||||||
- db-data:/var/lib/postgresql/data
|
- db-data:/var/lib/postgresql/data
|
||||||
# Crée le rôle applicatif à la première initialisation du volume.
|
# Crée le rôle applicatif à la première initialisation du volume.
|
||||||
- ./docker/init-app-role.sh:/docker-entrypoint-initdb.d/10-init-app-role.sh:ro
|
- ./docker/init-app-role.sh:/docker-entrypoint-initdb.d/10-init-app-role.sh:ro
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER:-planflow} -d ${POSTGRES_DB:-planflow}']
|
test:
|
||||||
|
[
|
||||||
|
'CMD-SHELL',
|
||||||
|
'pg_isready -p ${POSTGRES_PORT_INTERNAL:-5439} -U ${POSTGRES_USER:-planflow} -d ${POSTGRES_DB:-planflow}',
|
||||||
|
]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
@@ -78,10 +87,9 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- interne
|
- interne
|
||||||
- nginx_default
|
- nginx_default
|
||||||
# Le conteneur reste sur 5432 pour l'application, et le port hôte est publié
|
# Ni le port du conteneur ni celui de l'hôte ne sont les ports usuels :
|
||||||
# sur un port non commun, configurable : plusieurs instances PostgreSQL
|
# plusieurs instances PostgreSQL coexistent sur un même serveur, et s'en
|
||||||
# coexistent sur le même serveur, et s'en tenir au 5432 par défaut les
|
# tenir au 5432 partout invite la confusion.
|
||||||
# ferait entrer en collision.
|
|
||||||
#
|
#
|
||||||
# **Lié à la boucle locale**, et c'est important : la publication sert à se
|
# **Lié à la boucle locale**, et c'est important : la publication sert à se
|
||||||
# connecter depuis le serveur (sauvegarde, psql), pas depuis le réseau. Sans
|
# connecter depuis le serveur (sauvegarde, psql), pas depuis le réseau. Sans
|
||||||
@@ -89,7 +97,7 @@ services:
|
|||||||
# données RH derrière un mot de passe par défaut, joignable depuis
|
# données RH derrière un mot de passe par défaut, joignable depuis
|
||||||
# l'extérieur. Pour un accès distant, passer par un tunnel.
|
# l'extérieur. Pour un accès distant, passer par un tunnel.
|
||||||
ports:
|
ports:
|
||||||
- '${POSTGRES_BIND:-127.0.0.1}:${POSTGRES_PORT:-55432}:5432'
|
- '${POSTGRES_BIND:-127.0.0.1}:${POSTGRES_PORT:-55432}:${POSTGRES_PORT_INTERNAL:-5439}'
|
||||||
|
|
||||||
# Pose le rôle applicatif à **chaque** démarrage de la pile.
|
# Pose le rôle applicatif à **chaque** démarrage de la pile.
|
||||||
#
|
#
|
||||||
@@ -110,6 +118,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
PGHOST: db
|
PGHOST: db
|
||||||
|
PGPORT: ${POSTGRES_PORT_INTERNAL:-5439}
|
||||||
PGPASSWORD: ${POSTGRES_PASSWORD:-planflow-interne}
|
PGPASSWORD: ${POSTGRES_PASSWORD:-planflow-interne}
|
||||||
POSTGRES_USER: ${POSTGRES_USER:-planflow}
|
POSTGRES_USER: ${POSTGRES_USER:-planflow}
|
||||||
POSTGRES_DB: ${POSTGRES_DB:-planflow}
|
POSTGRES_DB: ${POSTGRES_DB:-planflow}
|
||||||
@@ -134,10 +143,14 @@ services:
|
|||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
|
# Port d'écoute du serveur **dans** le conteneur. C'est celui que vise
|
||||||
|
# le reverse-proxy ; il doit rester le même que celui du healthcheck et
|
||||||
|
# de la publication, d'où l'unique variable.
|
||||||
|
PORT: ${APP_PORT_INTERNAL:-9317}
|
||||||
# Le compte applicatif, **pas** le superutilisateur d'amorçage : un
|
# Le compte applicatif, **pas** le superutilisateur d'amorçage : un
|
||||||
# superutilisateur contourne la row-level security, y compris déclarée en
|
# superutilisateur contourne la row-level security, y compris déclarée en
|
||||||
# FORCE, et l'isolation ne reposerait plus que sur la couche applicative.
|
# FORCE, et l'isolation ne reposerait plus que sur la couche applicative.
|
||||||
DATABASE_URL: postgresql://${APP_DB_USER:-planflow_app}:${APP_DB_PASSWORD:-planflow-app-interne}@db:5432/${POSTGRES_DB:-planflow}
|
DATABASE_URL: postgresql://${APP_DB_USER:-planflow_app}:${APP_DB_PASSWORD:-planflow-app-interne}@db:${POSTGRES_PORT_INTERNAL:-5439}/${POSTGRES_DB:-planflow}
|
||||||
# Laissée vide, elle est **produite au premier démarrage** et conservée
|
# Laissée vide, elle est **produite au premier démarrage** et conservée
|
||||||
# dans le volume `secrets`. La renseigner ici reste possible pour un
|
# dans le volume `secrets`. La renseigner ici reste possible pour un
|
||||||
# déploiement qui gère ses secrets par ailleurs — mais une variable
|
# déploiement qui gère ses secrets par ailleurs — mais une variable
|
||||||
@@ -158,6 +171,7 @@ services:
|
|||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-planflow-interne}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-planflow-interne}
|
||||||
POSTGRES_DB: ${POSTGRES_DB:-planflow}
|
POSTGRES_DB: ${POSTGRES_DB:-planflow}
|
||||||
POSTGRES_HOST: db
|
POSTGRES_HOST: db
|
||||||
|
POSTGRES_PORT_INTERNAL: ${POSTGRES_PORT_INTERNAL:-5439}
|
||||||
APP_DB_USER: ${APP_DB_USER:-planflow_app}
|
APP_DB_USER: ${APP_DB_USER:-planflow_app}
|
||||||
APP_DB_PASSWORD: ${APP_DB_PASSWORD:-planflow-app-interne}
|
APP_DB_PASSWORD: ${APP_DB_PASSWORD:-planflow-app-interne}
|
||||||
volumes:
|
volumes:
|
||||||
@@ -167,21 +181,23 @@ services:
|
|||||||
- secrets:/secrets
|
- secrets:/secrets
|
||||||
networks:
|
networks:
|
||||||
# `interne` pour joindre la base, `nginx_default` pour être joignable par
|
# `interne` pour joindre la base, `nginx_default` pour être joignable par
|
||||||
# le reverse-proxy — qui atteint le conteneur sur son port 3000, sans
|
# le reverse-proxy — qui atteint le conteneur sur son port interne, sans
|
||||||
# passer par le port publié.
|
# passer par le port publié.
|
||||||
- interne
|
- interne
|
||||||
- nginx_default
|
- nginx_default
|
||||||
ports:
|
ports:
|
||||||
# Port peu courant : le service est censé passer par le reverse-proxy, et
|
# Port peu courant : le service est censé passer par le reverse-proxy, et
|
||||||
# un 3000 publié sur l'hôte se heurte à tout ce qui traîne.
|
# un 3000 publié sur l'hôte se heurte à tout ce qui traîne. Le port
|
||||||
- '${APP_PORT:-9317}:3000'
|
# **interne** l'est aussi, pour que la configuration du reverse-proxy
|
||||||
|
# porte partout la même valeur.
|
||||||
|
- '${APP_PORT:-9317}:${APP_PORT_INTERNAL:-9317}'
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test:
|
test:
|
||||||
[
|
[
|
||||||
'CMD',
|
'CMD',
|
||||||
'node',
|
'node',
|
||||||
'-e',
|
'-e',
|
||||||
"fetch('http://127.0.0.1:3000/api/sante').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
|
"fetch('http://127.0.0.1:${APP_PORT_INTERNAL:-9317}/api/sante').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
|
||||||
]
|
]
|
||||||
interval: 15s
|
interval: 15s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ const superUser = process.env.POSTGRES_USER ?? 'planflow';
|
|||||||
const superPassword = process.env.POSTGRES_PASSWORD ?? '';
|
const superPassword = process.env.POSTGRES_PASSWORD ?? '';
|
||||||
const database = process.env.POSTGRES_DB ?? 'planflow';
|
const database = process.env.POSTGRES_DB ?? 'planflow';
|
||||||
const host = process.env.POSTGRES_HOST ?? 'db';
|
const host = process.env.POSTGRES_HOST ?? 'db';
|
||||||
const port = Number(process.env.POSTGRES_PORT_INTERNAL ?? 5432);
|
// Le port d'écoute **dans** le conteneur de la base, que la pile choisit peu
|
||||||
|
// commun. Le défaut suit celui du compose, pas celui de PostgreSQL : une
|
||||||
|
// valeur qui diverge se solderait par un refus de connexion à l'amorçage.
|
||||||
|
const port = Number(process.env.POSTGRES_PORT_INTERNAL ?? 5439);
|
||||||
|
|
||||||
const appRole = process.env.APP_DB_USER ?? 'planflow_app';
|
const appRole = process.env.APP_DB_USER ?? 'planflow_app';
|
||||||
const appPassword = process.env.APP_DB_PASSWORD ?? 'planflow-app-interne';
|
const appPassword = process.env.APP_DB_PASSWORD ?? 'planflow-app-interne';
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ DB_USER="${POSTGRES_USER:-planflow}"
|
|||||||
# Dans le conteneur `db`, l'hôte est local ; depuis `db-init`, c'est `db`.
|
# Dans le conteneur `db`, l'hôte est local ; depuis `db-init`, c'est `db`.
|
||||||
# L'un et l'autre doivent aboutir au même SQL.
|
# L'un et l'autre doivent aboutir au même SQL.
|
||||||
if [ -n "${PGHOST:-}" ]; then
|
if [ -n "${PGHOST:-}" ]; then
|
||||||
set -- -h "$PGHOST" -p "${PGPORT:-5432}" -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1
|
# Défaut aligné sur celui de la pile, et non sur celui de PostgreSQL : la
|
||||||
|
# base écoute un port peu commun, et 5432 ne joindrait rien.
|
||||||
|
set -- -h "$PGHOST" -p "${PGPORT:-5439}" -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1
|
||||||
else
|
else
|
||||||
set -- -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1
|
set -- -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1
|
||||||
fi
|
fi
|
||||||
|
|||||||
+12
-1
@@ -819,7 +819,18 @@ async function seedPlanning(accountId: string): Promise<void> {
|
|||||||
isoWeek: week.isoWeek,
|
isoWeek: week.isoWeek,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: {},
|
// L'état de publication est **réimposé**, pas seulement posé à la
|
||||||
|
// création. Un seed qui laisse en place ce qu'il trouve ne produit un
|
||||||
|
// état de départ connu que la première fois : la semaine précédente,
|
||||||
|
// publiée par définition, reste en brouillon dès lors qu'elle a déjà
|
||||||
|
// été semée quand elle était la semaine courante. Les tests qui
|
||||||
|
// s'appuient dessus échouent alors sur une base de développement et
|
||||||
|
// passent en intégration continue, où le semis est neuf — l'écart le
|
||||||
|
// plus coûteux à diagnostiquer.
|
||||||
|
update: {
|
||||||
|
status: week === previous ? 'PUBLISHED' : 'DRAFT',
|
||||||
|
publishedAt: week === previous ? new Date() : null,
|
||||||
|
},
|
||||||
create: {
|
create: {
|
||||||
accountId,
|
accountId,
|
||||||
teamId: team.id,
|
teamId: team.id,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
import { formatMonthParam, monthOf, previousMonth } from '../../src/domain/planning/month';
|
import { formatMonthParam, monthOf } from '../../src/domain/planning/month';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Heures et périodes de paie.
|
* Heures et périodes de paie.
|
||||||
@@ -10,7 +10,22 @@ import { formatMonthParam, monthOf, previousMonth } from '../../src/domain/plann
|
|||||||
* plutôt qu'un bouton.
|
* plutôt qu'un bouton.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const MONTH = formatMonthParam(previousMonth(monthOf(new Date())));
|
/**
|
||||||
|
* Mois **courant**, et non le précédent.
|
||||||
|
*
|
||||||
|
* Le seed ne pose que deux semaines de planning : la courante et la
|
||||||
|
* précédente. Le mois précédent n'en contient donc aucune, sauf quand on joue
|
||||||
|
* la suite dans les premiers jours d'un mois — le tableau est vide le reste du
|
||||||
|
* temps, et l'assertion échoue pour une raison sans rapport avec ce qu'elle
|
||||||
|
* vérifie. Le défaut ne se voyait pas sur une base de développement, qui garde
|
||||||
|
* les créneaux des exécutions précédentes ; l'intégration continue, elle,
|
||||||
|
* repart d'un semis neuf.
|
||||||
|
*
|
||||||
|
* Le mois courant, lui, contient toujours aujourd'hui — donc toujours des
|
||||||
|
* créneaux, et aucun n'a d'heures réelles puisque rien dans le semis ni dans la
|
||||||
|
* suite n'en saisit.
|
||||||
|
*/
|
||||||
|
const MONTH = formatMonthParam(monthOf(new Date()));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mois propre à cette exécution.
|
* Mois propre à cette exécution.
|
||||||
|
|||||||
Reference in new issue
Block a user