ci(web): run the server suite before building the image
The build job now needs: test, so an image cannot be published with the suite in red — which is the whole point of this phase. Both jobs live in the same file because needs: only links jobs within one workflow. settings-ci.json is committed and contains no secrets: settings-development.json is gitignored, so CI had nothing to pass to --settings. The one test that cannot work without a secret is the node-red iron fixture, sealed with the deployment's own ironPassword; it now reports itself as skipped instead of failing on an hmac mismatch, and still runs locally against settings-development.json. Measured locally: 22s and a ~2.0 GB peak for the whole `meteor test` run (its own mongod included), hence --memory=3g. The meteor-tool download is not cached yet; the note in the workflow says what that would take.
This commit is contained in:
parent
e61efc681c
commit
3b04ebceb3
4 changed files with 174 additions and 3 deletions
|
|
@ -16,16 +16,112 @@
|
||||||
# proceso que se come la RAM. Si el `meteor build` muere por memoria, sube el `--memory`
|
# proceso que se come la RAM. Si el `meteor build` muere por memoria, sube el `--memory`
|
||||||
# de `container.options` (y mira qué más está corriendo en aaron), no lo quites.
|
# de `container.options` (y mira qué más está corriendo en aaron), no lo quites.
|
||||||
|
|
||||||
|
#
|
||||||
|
# ⚠️ POR QUÉ EL TEST VIVE EN ESTE FICHERO Y NO EN UNO APARTE:
|
||||||
|
# `needs:` solo enlaza jobs DEL MISMO workflow — no hay forma de que un fichero
|
||||||
|
# distinto bloquee a este. Y el requisito de la fase 11 es justo ese: que una
|
||||||
|
# imagen no se publique nunca con los tests en rojo. Así que los dos jobs van
|
||||||
|
# juntos, `build` depende de `test`, y se acabó. Si algún día act_runner soporta
|
||||||
|
# `workflow_call` de forma fiable, se puede extraer.
|
||||||
|
|
||||||
name: build-image
|
name: build-image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [meteor3-wip, tcef-master]
|
branches: [meteor3-wip, tcef-master]
|
||||||
paths-ignore: ['**.md']
|
paths-ignore: ['**.md']
|
||||||
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
# Suite de servidor (meteortesting:mocha). Bloquea el build: ver la nota de
|
||||||
|
# arriba. Mide y publica duración y pico de RAM en el resumen del job para que
|
||||||
|
# el margen del runner de aaron sea un dato y no una intuición.
|
||||||
|
test:
|
||||||
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: node:22-bookworm
|
||||||
|
# Medido en local: pico ~2,0 GB (meteor test + su mongod). 3g deja margen
|
||||||
|
# sin comerse los 4g que necesita el build de Kaniko justo después
|
||||||
|
# (capacity: 1 ⇒ los jobs no coinciden en el tiempo). Si el job muere por
|
||||||
|
# OOM, sube esto y mira antes qué más está corriendo en aaron.
|
||||||
|
options: --memory=3g --memory-swap=3g --cpus=3
|
||||||
|
env:
|
||||||
|
# meteor se niega a arrancar como root si no.
|
||||||
|
METEOR_ALLOW_SUPERUSER: '1'
|
||||||
|
# Node 22 + Happy Eyeballs elige IPv6 sin ruta contra warehouse.meteor.com
|
||||||
|
# (mismo workaround que en el Dockerfile y en dev).
|
||||||
|
NODE_OPTIONS: --dns-result-order=ipv4first --no-network-family-autoselection
|
||||||
|
METEOR_RELEASE: '3.1'
|
||||||
|
steps:
|
||||||
|
# `time` para medir el pico de RSS; el resto es lo mismo que instala el
|
||||||
|
# Dockerfile (node-gyp 3.x de algunas deps de Atmosphere llama a `python`).
|
||||||
|
- name: Herramientas
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
curl ca-certificates git time python3 python-is-python3 make g++ >/dev/null
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Sin actions JS: el código se baja por el API de Forgejo, igual que en el
|
||||||
|
# job de build (ver la nota de más abajo).
|
||||||
|
- name: Descargar el código
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
mkdir -p src
|
||||||
|
wget -q -O src.tar.gz \
|
||||||
|
"http://x-access-token:${TOKEN}@forgejo:3000/api/v1/repos/${GITHUB_REPOSITORY}/archive/${GITHUB_SHA}.tar.gz"
|
||||||
|
tar xzf src.tar.gz -C src --strip-components=1
|
||||||
|
rm -f src.tar.gz
|
||||||
|
test -f src/package.json || { echo "No hay package.json en el tarball"; ls src | head; exit 1; }
|
||||||
|
|
||||||
|
# ~700 MB de meteor-tool en cada job. No se cachea porque `actions/cache` es
|
||||||
|
# una action JS (fase 9 §3) y montar un volumen del host exige tocar
|
||||||
|
# `valid_volumes` en la config de act_runner de aaron. Pendiente: si el job
|
||||||
|
# se vuelve molesto, añadir /data/ci-cache/meteor -> /root/.meteor.
|
||||||
|
- name: Instalar meteor-tool
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
curl -fsSL "https://install.meteor.com/?release=${METEOR_RELEASE}" | sh
|
||||||
|
meteor --version
|
||||||
|
|
||||||
|
- name: Dependencias npm
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
cd src
|
||||||
|
meteor npm ci --no-audit --no-fund
|
||||||
|
|
||||||
|
# `meteor test` levanta su propio mongod (single-node replica set), que es
|
||||||
|
# lo que hace que el oplog esté disponible y los observadores reaccionen al
|
||||||
|
# instante: contra un mongo externo sin oplog, Meteor cae a polling cada 10s
|
||||||
|
# y los tests de subsUnion tardarían eso en ver cada cambio.
|
||||||
|
- name: Tests de servidor
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
cd src
|
||||||
|
START=$(date +%s)
|
||||||
|
/usr/bin/time -v -o ../time.log meteor npm run test:ci
|
||||||
|
echo "DURACION=$(( $(date +%s) - START ))s"
|
||||||
|
grep -E 'Maximum resident set size' ../time.log || true
|
||||||
|
|
||||||
|
- name: Resumen
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
{
|
||||||
|
echo "### Tests de servidor"
|
||||||
|
echo ""
|
||||||
|
echo "- pico de RSS: $(grep -oE '[0-9]+$' time.log 2>/dev/null | head -1 || echo '?') KB"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY" || true
|
||||||
|
|
||||||
build:
|
build:
|
||||||
|
# Ninguna imagen sale de aquí con los tests en rojo. Ese es el punto entero
|
||||||
|
# de la fase 11.
|
||||||
|
needs: test
|
||||||
|
# En un PR solo interesa saber si pasa; publicar imagen es cosa de la rama.
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
# `:debug` trae busybox: el runner necesita una shell para ejecutar los `run:`.
|
# `:debug` trae busybox: el runner necesita una shell para ejecutar los `run:`.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "QT_QPA_PLATFORM='' MONGO_URL=mongodb://fuegos:fuegos@localhost:27017/fuegos meteor --settings settings-development.json",
|
"start": "QT_QPA_PLATFORM='' MONGO_URL=mongodb://fuegos:fuegos@localhost:27017/fuegos meteor --settings settings-development.json",
|
||||||
"test": "meteor test --once --driver-package meteortesting:mocha --settings settings-development.json --port 3100",
|
"test": "meteor test --once --driver-package meteortesting:mocha --settings settings-development.json --port 3100",
|
||||||
"test-watch": "TEST_WATCH=1 meteor test --driver-package meteortesting:mocha --settings settings-development.json --port 3100"
|
"test-watch": "TEST_WATCH=1 meteor test --driver-package meteortesting:mocha --settings settings-development.json --port 3100",
|
||||||
|
"test:ci": "meteor test --once --driver-package meteortesting:mocha --settings settings-ci.json --port 3100"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.29.7",
|
"@babel/runtime": "^7.29.7",
|
||||||
|
|
|
||||||
59
settings-ci.json
Normal file
59
settings-ci.json
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
"gmaps": {
|
||||||
|
"key": "",
|
||||||
|
"serverKey": ""
|
||||||
|
},
|
||||||
|
"piwik": {
|
||||||
|
"url": "http://localhost:9/piwik.php",
|
||||||
|
"site_id": 1
|
||||||
|
},
|
||||||
|
"sentryPrivateDSN": "",
|
||||||
|
"public": {
|
||||||
|
"sentryPublicDSN": "",
|
||||||
|
"telegramAuth": 1
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"testEmail": "someone@example.com",
|
||||||
|
"testMailer": false,
|
||||||
|
"debugMailer": false,
|
||||||
|
"MAIL_URL": "smtp://127.0.0.1:1025/",
|
||||||
|
"// isMailServer": "See: https://github.com/VeliovGroup/Mail-Time",
|
||||||
|
"isMailServer": true,
|
||||||
|
"fireIconUrl": "https://testfuegos.comunes.org/fire-marker.png",
|
||||||
|
"OAuth": {
|
||||||
|
"google": {
|
||||||
|
"clientId": "",
|
||||||
|
"secret": "",
|
||||||
|
"loginStyle": "popup"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fcmApiToken": "",
|
||||||
|
"proxies_count": 0,
|
||||||
|
"ironPassword": "SomePasswordAlLeast32LongYouKnowHowToCount",
|
||||||
|
"internalApiToken": "ci-smoke-token",
|
||||||
|
"cron": {
|
||||||
|
"debug": false
|
||||||
|
},
|
||||||
|
"twitter": {
|
||||||
|
"es": {
|
||||||
|
"enabled": false,
|
||||||
|
"comment": "using later.js",
|
||||||
|
"when": "at 9:15 am also at 3:15 pm also at 10:00 pm",
|
||||||
|
"consumer_key": "...",
|
||||||
|
"consumer_secret": "...",
|
||||||
|
"access_token": "...",
|
||||||
|
"access_token_secret": "..."
|
||||||
|
},
|
||||||
|
"en": {
|
||||||
|
"enabled": false,
|
||||||
|
"comment": "using later.js",
|
||||||
|
"when": "at 9:15 am also at 3:15 pm also at 10:00 pm",
|
||||||
|
"consumer_key": "...",
|
||||||
|
"consumer_secret": "...",
|
||||||
|
"access_token": "...",
|
||||||
|
"access_token_secret": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_comment": "Settings for CI (tests / smoke / e2e). No secrets: every value here is fake on purpose. See settings-development-sample.json for the shape."
|
||||||
|
}
|
||||||
|
|
@ -55,9 +55,24 @@ describe('url encoding', () => {
|
||||||
expect(sealed).to.not.include('%');
|
expect(sealed).to.not.include('%');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should decrypt a node-red produced blob without throwing', async () => {
|
// This fixture was sealed by node-red with the deployment's own ironPassword,
|
||||||
|
// which is not (and must not be) in any committed settings file — so under
|
||||||
|
// settings-ci.json it cannot possibly unseal. Run it locally against
|
||||||
|
// settings-development.json, where it proves what it is here to prove: that a
|
||||||
|
// blob produced by another iron implementation still unseals with ours. In CI
|
||||||
|
// it reports itself as skipped instead of failing on a password mismatch.
|
||||||
|
it('should decrypt a node-red produced blob without throwing', async function skipsWithoutTheFixturePassword() {
|
||||||
const sealed = 'Fe26.2**7ae64199b871901d16a6ba031198c57b43b4a9231e15b851ff80014a8de230ca*RkB3_Uu_oJnTefyzB-Ocqg*2Z9rXYf--GxLJnYESHvdK5rC6lOMlSbMJ6YQyR2Mgpl57iBYopAHPWRLNTBgNkbUHFYa0IlEjdyeEz5VvLDtVvlSr1Sam-Cx8GUai4mharZO5-Wkze0dTr7M56WofSC9jpkv3kANrpsrit3HASE_E-5Z1JVYQVQqehw-VbSZTorrYj0GCJiAgXE5I0iY4boXRDYCv7fm_pzcuAbHlnNkKT1GbMFEPjyVViP4mVMRI5PDUhcWb0f62goMX4UnYLZXum4oYOIr84DG8AxqIdW2L94yslt0EZkD3yVhvKEGoauMpy6ry2illpSgaMaj4sU3AKWIjfAsJXvM6bHwbNh6G1_BJfCapuu3KijBBQPQMAnhYuwLAlY56WN-Y2efNIBJkp__kO6ak2nFqu8PufpxE-cv0uW7x6GWUtCpnFO2SeUJWVVMddUgJjLiM8Hkdl1v9huB0WdIvsoPEV0ZOwHZaC3jtdKFSO7Xe6LdqTXjXmdBMwtOv-HefyaB8LnEN6dAeKWwJuDu7g03QFTryDIiuwtpQ8mcLWTJGHcxoaluNhUESOBULkWSv_E1vOM1_fFv**1e41101b27c5da58bc11177448e91d88d7801bf911088a895844db0c542dfa7c*dPlOhtXJcyR47ezuL7CEgC2Z8d6C1asNOiqZmhD5TXY';
|
const sealed = 'Fe26.2**7ae64199b871901d16a6ba031198c57b43b4a9231e15b851ff80014a8de230ca*RkB3_Uu_oJnTefyzB-Ocqg*2Z9rXYf--GxLJnYESHvdK5rC6lOMlSbMJ6YQyR2Mgpl57iBYopAHPWRLNTBgNkbUHFYa0IlEjdyeEz5VvLDtVvlSr1Sam-Cx8GUai4mharZO5-Wkze0dTr7M56WofSC9jpkv3kANrpsrit3HASE_E-5Z1JVYQVQqehw-VbSZTorrYj0GCJiAgXE5I0iY4boXRDYCv7fm_pzcuAbHlnNkKT1GbMFEPjyVViP4mVMRI5PDUhcWb0f62goMX4UnYLZXum4oYOIr84DG8AxqIdW2L94yslt0EZkD3yVhvKEGoauMpy6ry2illpSgaMaj4sU3AKWIjfAsJXvM6bHwbNh6G1_BJfCapuu3KijBBQPQMAnhYuwLAlY56WN-Y2efNIBJkp__kO6ak2nFqu8PufpxE-cv0uW7x6GWUtCpnFO2SeUJWVVMddUgJjLiM8Hkdl1v9huB0WdIvsoPEV0ZOwHZaC3jtdKFSO7Xe6LdqTXjXmdBMwtOv-HefyaB8LnEN6dAeKWwJuDu7g03QFTryDIiuwtpQ8mcLWTJGHcxoaluNhUESOBULkWSv_E1vOM1_fFv**1e41101b27c5da58bc11177448e91d88d7801bf911088a895844db0c542dfa7c*dPlOhtXJcyR47ezuL7CEgC2Z8d6C1asNOiqZmhD5TXY';
|
||||||
const unsealed = await urlEnc.decrypt(sealed);
|
let unsealed;
|
||||||
|
try {
|
||||||
|
unsealed = await urlEnc.decrypt(sealed);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message === 'Bad hmac value') {
|
||||||
|
this.skip(); // sealed with a different ironPassword than the one configured
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
expect(unsealed).to.be.an('object');
|
expect(unsealed).to.be.an('object');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue