Comparing version 0.0.75 to 0.0.76
{ | ||
"name": "@lbu/cli", | ||
"version": "0.0.75", | ||
"version": "0.0.76", | ||
"description": "CLI containing utilities and simple script runner", | ||
@@ -30,4 +30,4 @@ "main": "./index.js", | ||
"dependencies": { | ||
"@lbu/insight": "0.0.75", | ||
"@lbu/stdlib": "0.0.75", | ||
"@lbu/insight": "0.0.76", | ||
"@lbu/stdlib": "0.0.76", | ||
"c8": "7.3.1", | ||
@@ -59,3 +59,3 @@ "chokidar": "3.4.2", | ||
}, | ||
"gitHead": "b89ec55fceee61902fe1ee79e4c658ff70f0b129" | ||
"gitHead": "4d5245ee1c104c3fb11e37afd6a9a226fa29706c" | ||
} |
@@ -5,7 +5,9 @@ import { exec, spawn } from "@lbu/stdlib"; | ||
const supportedContainers = { | ||
"lbu-postgres": { | ||
createCommand: | ||
"docker create -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e PGDATA=/var/lib/postgresql/data/pgdata -v lbu-postgres:/var/lib/postgresql/data/pgdata -p 5432:5432 --name lbu-postgres postgres:12", | ||
const containers = { | ||
"lbu-postgres-12": { | ||
createCommand: `docker create -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e PGDATA=/var/lib/postgresql/data/pgdata -v lbu-postgres-12:/var/lib/postgresql/data/pgdata -p 5432:5432 --name lbu-postgres-12 postgres:12`, | ||
}, | ||
"lbu-postgres-13": { | ||
createCommand: `docker create -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e PGDATA=/var/lib/postgresql/data/pgdata -v lbu-postgres-13:/var/lib/postgresql/data/pgdata -p 5432:5432 --name lbu-postgres-13 postgres:13`, | ||
}, | ||
"lbu-minio": { | ||
@@ -39,10 +41,36 @@ createCommand: `docker create -e MINIO_ACCESS_KEY=minio -e MINIO_SECRET_KEY=minio123 -v lbu-minio:/data -p 9000:9000 --name lbu-minio minio/minio server /data`, | ||
const { | ||
knownContainers, | ||
exitCode, | ||
stdout, | ||
stderr, | ||
} = await getKnownContainers(); | ||
if (exitCode !== 0) { | ||
logger.error(`Could not list containers.`); | ||
logger.error({ exitCode, stderr, stdout }); | ||
} | ||
const enabledContainers = [ | ||
`lbu-postgres-${getPostgresVersion()}`, | ||
`lbu-minio`, | ||
]; | ||
const disabledContainers = Object.keys(containers).filter( | ||
(it) => enabledContainers.indexOf(it) === -1, | ||
); | ||
const containerInfo = { | ||
knownContainers, | ||
enabledContainers, | ||
disabledContainers, | ||
}; | ||
if (subCommand === "up") { | ||
return { exitCode: await startContainers(logger) }; | ||
return { exitCode: await startContainers(logger, containerInfo) }; | ||
} else if (subCommand === "down") { | ||
return { exitCode: await stopContainers(logger) }; | ||
return { exitCode: await stopContainers(logger, containerInfo) }; | ||
} else if (subCommand === "clean") { | ||
return { exitCode: await cleanContainers(logger) }; | ||
return { exitCode: await cleanContainers(logger, containerInfo) }; | ||
} else if (subCommand === "reset") { | ||
return { exitCode: await resetDatabase(logger) }; | ||
return { exitCode: await resetDatabase(logger, containerInfo) }; | ||
} | ||
@@ -53,16 +81,20 @@ } | ||
* @param {Logger} logger | ||
* @param {{ enabledContainers: string[], disabledContainers: string[], knownContainers: | ||
* string[] }} containerInfo | ||
* @returns {Promise<number>} | ||
*/ | ||
async function startContainers(logger) { | ||
const { stdout, exitCode: listContainersExit } = await exec( | ||
"docker container ls -a --format '{{.Names}}'", | ||
); | ||
if (listContainersExit !== 0) { | ||
return listContainersExit; | ||
async function startContainers(logger, containerInfo) { | ||
const stopExitCode = await stopContainers(logger, { | ||
knownContainers: containerInfo.knownContainers, | ||
enabledContainers: containerInfo.disabledContainers, | ||
}); | ||
if (stopExitCode !== 0) { | ||
return stopExitCode; | ||
} | ||
for (const name of Object.keys(supportedContainers)) { | ||
for (const name of containerInfo.enabledContainers) { | ||
logger.info(`Creating ${name} container`); | ||
if (stdout.indexOf(name) === -1) { | ||
const { exitCode } = await exec(supportedContainers[name].createCommand); | ||
if (containerInfo.knownContainers.indexOf(name) === -1) { | ||
const { exitCode } = await exec(containers[name].createCommand); | ||
if (exitCode !== 0) { | ||
@@ -77,5 +109,21 @@ return exitCode; | ||
"start", | ||
...Object.keys(supportedContainers), | ||
...containerInfo.enabledContainers, | ||
]); | ||
if (exitCode !== 0) { | ||
return exitCode; | ||
} | ||
logger.info(`Waiting for Postgres ${getPostgresVersion()} to be ready...`); | ||
// Race for 30 seconds against the pg_isready command | ||
await Promise.race([ | ||
exec( | ||
`until docker exec lbu-postgres-${getPostgresVersion()} pg_isready ; do sleep 1 ; done`, | ||
{ shell: true }, | ||
), | ||
new Promise((resolve, reject) => { | ||
setTimeout(reject, 30000); | ||
}), | ||
]); | ||
return exitCode; | ||
@@ -85,11 +133,22 @@ } | ||
/** | ||
* Stops 'available' containers. | ||
* By using the `knownContainers` as a check, we don't error when a container is not yet | ||
* created. | ||
* | ||
* @param {Logger} logger | ||
* @param {{ enabledContainers: string[], disabledContainers: string[], knownContainers: | ||
* string[] }} containerInfo | ||
* @returns {Promise<number>} | ||
*/ | ||
async function stopContainers(logger) { | ||
async function stopContainers(logger, containerInfo) { | ||
const containers = containerInfo.enabledContainers.filter( | ||
(it) => containerInfo.knownContainers.indexOf(it) !== -1, | ||
); | ||
if (containers.length === 0) { | ||
return 0; | ||
} | ||
logger.info(`Stopping containers`); | ||
const { exitCode } = await spawn(`docker`, [ | ||
"stop", | ||
...Object.keys(supportedContainers), | ||
]); | ||
const { exitCode } = await spawn(`docker`, ["stop", ...containers]); | ||
@@ -100,7 +159,13 @@ return exitCode; | ||
/** | ||
* Cleanup 'available' known containers. | ||
* By using the `knownContainers` as a check, we don't error when a container is not yet | ||
* created. | ||
* | ||
* @param {Logger} logger | ||
* @param {{ enabledContainers: string[], disabledContainers: string[], knownContainers: | ||
* string[] }} containerInfo | ||
* @returns {Promise<number>} | ||
*/ | ||
async function cleanContainers(logger) { | ||
const stopExit = await stopContainers(logger); | ||
async function cleanContainers(logger, containerInfo) { | ||
const stopExit = await stopContainers(logger, containerInfo); | ||
if (stopExit !== 0) { | ||
@@ -111,5 +176,8 @@ return stopExit; | ||
logger.info(`Removing containers`); | ||
const containersAndVolumes = containerInfo.enabledContainers.filter( | ||
(it) => containerInfo.knownContainers.indexOf(it) !== -1, | ||
); | ||
const { exitCode: rmExit } = await spawn(`docker`, [ | ||
"rm", | ||
...Object.keys(supportedContainers), | ||
...containersAndVolumes, | ||
]); | ||
@@ -124,3 +192,3 @@ if (rmExit !== 0) { | ||
"rm", | ||
...Object.keys(supportedContainers), | ||
...containersAndVolumes, | ||
]); | ||
@@ -133,8 +201,10 @@ | ||
* @param {Logger} logger | ||
* @param {{ enabledContainers: string[], disabledContainers: string[], knownContainers: | ||
* string[] }} containerInfo | ||
* @returns {Promise<number>} | ||
*/ | ||
async function resetDatabase(logger) { | ||
const startExit = await startContainers(logger); | ||
if (startExit !== 0) { | ||
return startExit; | ||
async function resetDatabase(logger, containerInfo) { | ||
const startExitCode = await startContainers(logger, containerInfo); | ||
if (startExitCode !== 0) { | ||
return startExitCode; | ||
} | ||
@@ -147,3 +217,3 @@ | ||
"-c", | ||
`echo 'DROP DATABASE ${name}; CREATE DATABASE ${name}' | docker exec -i lbu-postgres psql --user postgres`, | ||
`echo 'DROP DATABASE IF EXISTS ${name}; CREATE DATABASE ${name}' | docker exec -i lbu-postgres-${getPostgresVersion()} psql --user postgres`, | ||
]); | ||
@@ -169,1 +239,30 @@ | ||
} | ||
/** | ||
* Get list of available containers | ||
* | ||
* @returns {Promise<{ exitCode: number, knownContainers: string[], stdout?: string, | ||
* stderr?: string }>} | ||
*/ | ||
async function getKnownContainers() { | ||
const { exitCode, stdout, stderr } = await exec( | ||
"docker container ls -a --format '{{.Names}}'", | ||
); | ||
if (exitCode !== 0) { | ||
return { exitCode, knownContainers: [], stdout, stderr }; | ||
} | ||
const knownContainers = stdout | ||
.split("\n") | ||
.map((it) => it.trim()) | ||
.filter((it) => it.length > 0); | ||
return { | ||
exitCode, | ||
knownContainers, | ||
}; | ||
} | ||
function getPostgresVersion() { | ||
return Number(process.env.POSTGRES_VERSION ?? "12"); | ||
} |
@@ -1,2 +0,3 @@ | ||
import { deepStrictEqual, AssertionError } from "assert"; | ||
import { AssertionError, deepStrictEqual } from "assert"; | ||
import { cpus } from "os"; | ||
import { isNil } from "@lbu/stdlib"; | ||
@@ -9,2 +10,28 @@ import { setTestTimeout, state, testLogger, timeout } from "./state.js"; | ||
*/ | ||
export async function runTestChildrenInParallel(testState) { | ||
// This only provides a reasonable default of concurrency mostly useful in synchronous testing | ||
// We actually still use a single Node.js process and thus CPU bound to a single core. | ||
const maxParallel = Math.min(cpus().length, testState.children.length, 6); | ||
let testIndex = 0; | ||
const schedule = async () => { | ||
if (testIndex >= testState.children.length) { | ||
return; | ||
} | ||
const idx = testIndex; | ||
testIndex++; | ||
await runTestsRecursively(testState.children[idx]); | ||
return schedule(); | ||
}; | ||
const promiseArray = Array.from({ length: maxParallel }, () => schedule()); | ||
await Promise.all(promiseArray); | ||
} | ||
/** | ||
* @param {TestState} testState | ||
* @returns {Promise<void>} | ||
*/ | ||
export async function runTestsRecursively(testState) { | ||
@@ -106,2 +133,3 @@ const runner = createRunnerForState(testState); | ||
} | ||
/** | ||
@@ -108,0 +136,0 @@ * @param {TestState} state |
import { mainFn } from "@lbu/stdlib"; | ||
import { loadTestConfig } from "./config.js"; | ||
import { printTestResults } from "./printer.js"; | ||
import { runTestsRecursively } from "./runner.js"; | ||
import { runTestChildrenInParallel } from "./runner.js"; | ||
import { | ||
@@ -36,4 +36,5 @@ areTestsRunning, | ||
} | ||
await runTestsRecursively(state); | ||
await runTestChildrenInParallel(state); | ||
if (typeof config?.teardown === "function") { | ||
@@ -40,0 +41,0 @@ await config.teardown(); |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
356330
2485
17
+ Added@lbu/insight@0.0.76(transitive)
+ Added@lbu/stdlib@0.0.76(transitive)
- Removed@lbu/insight@0.0.75(transitive)
- Removed@lbu/stdlib@0.0.75(transitive)
Updated@lbu/insight@0.0.76
Updated@lbu/stdlib@0.0.76