Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] integration tests docker registry more robust #136438

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,52 @@
* 2.0.
*/

import { spawn } from 'child_process';
import type { ChildProcess } from 'child_process';

import * as Rx from 'rxjs';
import { filter, take, map, tap } from 'rxjs/operators';
import execa from 'execa';

import { observeLines } from '@kbn/stdio-dev-helpers';
import { ToolingLog } from '@kbn/tooling-log';
import pRetry from 'p-retry';
import fetch from 'node-fetch';

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const BEFORE_SETUP_TIMEOUT = 30 * 60 * 1000; // 30 minutes;

const DOCKER_START_TIMEOUT = 5 * 60 * 1000; // 5 minutes
const DOCKER_IMAGE = `docker.elastic.co/package-registry/distribution:93ffe45d8c4ae11365bc70b1038643121049b9fe`;

function firstWithTimeout(source$: Rx.Observable<any>, errorMsg: string, ms = 30 * 1000) {
return Rx.race(
source$.pipe(take(1)),
Rx.timer(ms).pipe(
map(() => {
throw new Error(`[docker:${DOCKER_IMAGE}] ${errorMsg} within ${ms / 1000} seconds`);
})
)
);
}

function childProcessToLogLine(childProcess: ChildProcess, log: ToolingLog) {
const logLine$ = new Rx.Subject<string>();

Rx.merge(
observeLines(childProcess.stdout!).pipe(
tap((line) => log.info(`[docker:${DOCKER_IMAGE}] ${line}`))
), // TypeScript note: As long as the proc stdio[1] is 'pipe', then stdout will not be null
observeLines(childProcess.stderr!).pipe(
tap((line) => log.error(`[docker:${DOCKER_IMAGE}] ${line}`))
) // TypeScript note: As long as the proc stdio[2] is 'pipe', then stderr will not be null
).subscribe(logLine$);

return logLine$.asObservable();
}

export function useDockerRegistry() {
const logger = new ToolingLog({
level: 'info',
writeTo: process.stdout,
});
const packageRegistryPort = process.env.FLEET_PACKAGE_REGISTRY_PORT || '8081';

if (!packageRegistryPort.match(/^[0-9]{4}/)) {
Expand All @@ -24,38 +59,42 @@ export function useDockerRegistry() {

let dockerProcess: ChildProcess | undefined;
async function startDockerRegistryServer() {
const dockerImage = `docker.elastic.co/package-registry/distribution:93ffe45d8c4ae11365bc70b1038643121049b9fe`;
const args = ['run', '--rm', '-p', `${packageRegistryPort}:8080`, DOCKER_IMAGE];

const args = ['run', '--rm', '-p', `${packageRegistryPort}:8080`, dockerImage];

dockerProcess = spawn('docker', args, { stdio: 'inherit' });
dockerProcess = execa('docker', args, {
stdio: ['ignore', 'pipe', 'pipe'],
});

let isExited = dockerProcess.exitCode !== null;
dockerProcess.once('exit', () => {
isExited = true;
});

const startedAt = Date.now();

while (!isExited && Date.now() - startedAt <= DOCKER_START_TIMEOUT) {
try {
const res = await fetch(`http://localhost:${packageRegistryPort}/`);
if (res.status === 200) {
return;
}
} catch (err) {
// swallow errors
}

await delay(3000);
const waitForLogLine = /package manifests loaded/;

try {
await firstWithTimeout(
childProcessToLogLine(dockerProcess, logger).pipe(
filter((line) => {
process.stdout.write(line);
return waitForLogLine.test(line);
})
),
'no package manifests loaded',
DOCKER_START_TIMEOUT
).toPromise();
} catch (err) {
dockerProcess.kill();
throw err;
}

if (isExited && dockerProcess.exitCode !== 0) {
throw new Error(`Unable to setup docker registry exit code ${dockerProcess.exitCode}`);
}
}

dockerProcess.kill();
throw new pRetry.AbortError('Unable to setup docker registry after timeout');
async function pullDockerImage() {
logger.info(`[docker:${DOCKER_IMAGE}] pulling docker image "${DOCKER_IMAGE}"`);
await execa('docker', ['pull', DOCKER_IMAGE]);
}

async function cleanupDockerRegistryServer() {
Expand All @@ -65,8 +104,11 @@ export function useDockerRegistry() {
}

beforeAll(async () => {
const testTimeout = 5 * 60 * 1000; // 5 minutes timeout
jest.setTimeout(testTimeout);
jest.setTimeout(BEFORE_SETUP_TIMEOUT);
await pRetry(() => pullDockerImage(), {
retries: 3,
});

await pRetry(() => startDockerRegistryServer(), {
retries: 3,
});
Expand Down