feat(containers): introduce separate container commands (#17541)

This patch introduces hidden commands to control container
lifecycle:
- `npx playwright docker install-server-deps` to install fluxbox,
  vnc, novnc & to configure them.
- `npx playwright docker run-server` to run a server inside the
  container.

Drive-by: remove old version of container image when building a new
version with the same name. This way we won't pile up untagged
container images.
This commit is contained in:
Andrey Lushnikov 2022-09-22 16:38:54 -04:00 коммит произвёл GitHub
Родитель 6b4afbb8df
Коммит 4cd2176155
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 59 добавлений и 43 удалений

Просмотреть файл

@ -88,38 +88,3 @@ $center $full|/ms-playwright-agent/node_modules/playwright-core/lib/server/chrom
$center $full|/ms-playwright-agent/node_modules/playwright-core/lib/server/chromium/appIcon.png||:99.0
EOF
# Create entrypoint.sh
cat <<'EOF' > /entrypoint.sh
#!/bin/bash
set -e
SCREEN_WIDTH=1360
SCREEN_HEIGHT=1020
SCREEN_DEPTH=24
SCREEN_DPI=96
GEOMETRY="$SCREEN_WIDTH""x""$SCREEN_HEIGHT""x""$SCREEN_DEPTH"
nohup /usr/bin/xvfb-run --server-num=$DISPLAY_NUM \
--listen-tcp \
--server-args="-screen 0 "$GEOMETRY" -fbdir /var/tmp -dpi "$SCREEN_DPI" -listen tcp -noreset -ac +extension RANDR" \
/usr/bin/fluxbox -display "$DISPLAY" >/dev/null 2>&1 &
for i in $(seq 1 500); do
if xdpyinfo -display $DISPLAY >/dev/null 2>&1; then
break
fi
echo "Waiting for Xvfb..."
sleep 0.2
done
nohup x11vnc -noprimary -nosetprimary -forever -shared -rfbport 5900 -rfbportv6 5900 -display "$DISPLAY" >/dev/null 2>&1 &
nohup /opt/bin/noVNC/utils/novnc_proxy --listen 7900 --vnc localhost:5900 >/dev/null 2>&1 &
cd /ms-playwright-agent
NOVNC_UUID=$(cat /proc/sys/kernel/random/uuid)
echo "novnc is listening on http://127.0.0.1:7900?path=$NOVNC_UUID&resize=scale&autoconnect=1"
PW_UUID=$(cat /proc/sys/kernel/random/uuid)
npx playwright run-server --port=5400 --path=/$PW_UUID
EOF
chmod 755 /entrypoint.sh

Просмотреть файл

@ -0,0 +1,31 @@
#!/bin/bash
set -e
SCREEN_WIDTH=1360
SCREEN_HEIGHT=1020
SCREEN_DEPTH=24
SCREEN_DPI=96
GEOMETRY="$SCREEN_WIDTH""x""$SCREEN_HEIGHT""x""$SCREEN_DEPTH"
nohup /usr/bin/xvfb-run --server-num=$DISPLAY_NUM \
--listen-tcp \
--server-args="-screen 0 "$GEOMETRY" -fbdir /var/tmp -dpi "$SCREEN_DPI" -listen tcp -noreset -ac +extension RANDR" \
/usr/bin/fluxbox -display "$DISPLAY" >/dev/null 2>&1 &
for i in $(seq 1 500); do
if xdpyinfo -display $DISPLAY >/dev/null 2>&1; then
break
fi
echo "Waiting for Xvfb..."
sleep 0.2
done
nohup x11vnc -noprimary -nosetprimary -forever -shared -rfbport 5900 -rfbportv6 5900 -display "$DISPLAY" >/dev/null 2>&1 &
nohup /opt/bin/noVNC/utils/novnc_proxy --listen 7900 --vnc localhost:5900 >/dev/null 2>&1 &
cd /ms-playwright-agent
NOVNC_UUID=$(cat /proc/sys/kernel/random/uuid)
echo "novnc is listening on http://127.0.0.1:7900?path=$NOVNC_UUID&resize=scale&autoconnect=1"
PW_UUID=$(cat /proc/sys/kernel/random/uuid)
npx playwright run-server --port=5400 --path=/$PW_UUID

Просмотреть файл

@ -16,7 +16,6 @@
/* eslint-disable no-console */
import path from 'path';
import fs from 'fs';
import { spawnAsync } from '../utils/spawnAsync';
import * as utils from '../utils';
import { getPlaywrightVersion } from '../common/userAgent';
@ -102,13 +101,15 @@ async function buildPlaywrightImage() {
const dockerImage = await findDockerImage(baseImageName);
if (!dockerImage)
throw new Error(`Failed to pull ${baseImageName}`);
// 3. Launch container and install VNC in it
// 3. Delete previous build of the playwright image to avoid untagged images.
await deletePlaywrightImage();
// 4. Launch container and install VNC in it
console.log(`Building ${VRT_IMAGE_NAME}...`);
const buildScriptText = await fs.promises.readFile(path.join(__dirname, 'build_docker_image.sh'), 'utf8');
const containerId = await dockerApi.launchContainer({
imageId: dockerImage.imageId,
autoRemove: false,
command: ['/bin/bash', '-c', buildScriptText],
workingDir: '/ms-playwright-agent',
command: ['npx', 'playwright', 'docker', 'install-server-deps'],
waitUntil: 'not-running',
});
@ -118,7 +119,8 @@ async function buildPlaywrightImage() {
containerId,
repo: vrtRepo,
tag: vrtTag,
entrypoint: '/entrypoint.sh',
workingDir: '/ms-playwright-agent',
entrypoint: ['npx', 'playwright', 'docker', 'run-server'],
env: {
'DISPLAY_NUM': '99',
'DISPLAY': ':99',
@ -317,6 +319,20 @@ export function addDockerCLI(program: Command) {
}
});
dockerCommand.command('install-server-deps', { hidden: true })
.description('delete docker image, if any')
.action(async function() {
const { code } = await spawnAsync('bash', [path.join(__dirname, '..', '..', 'bin', 'container_install_deps.sh')], { stdio: 'inherit' });
if (code !== 0)
throw new Error('Failed to install server dependencies!');
});
dockerCommand.command('run-server', { hidden: true })
.description('delete docker image, if any')
.action(async function() {
await spawnAsync('bash', [path.join(__dirname, '..', '..', 'bin', 'container_run_server.sh')], { stdio: 'inherit' });
});
dockerCommand.command('print-status-json', { hidden: true })
.description('print docker status')
.action(async function(options) {

Просмотреть файл

@ -65,6 +65,7 @@ interface LaunchContainerOptions {
labels?: Record<string, string>;
ports?: Number[];
name?: string;
workingDir?: string;
waitUntil?: 'not-running' | 'next-exit' | 'removed';
}
@ -77,6 +78,7 @@ export async function launchContainer(options: LaunchContainerOptions): Promise<
}
const container = await postJSON(`/containers/create` + (options.name ? '?name=' + options.name : ''), {
Cmd: options.command,
WorkingDir: options.workingDir,
Labels: options.labels ?? {},
AttachStdout: true,
AttachStderr: true,
@ -134,7 +136,8 @@ interface CommitContainerOptions {
containerId: string,
repo: string,
tag: string,
entrypoint?: string,
entrypoint?: string[],
workingDir?: string,
env?: {[key: string]: string | number | boolean | undefined},
}
@ -143,7 +146,8 @@ export async function commitContainer(options: CommitContainerOptions) {
for (const [key, value] of Object.entries(options.env ?? {}))
Env.push(`${key}=${value}`);
await postJSON(`/commit?container=${options.containerId}&repo=${options.repo}&tag=${options.tag}`, {
Entrypoint: options.entrypoint ?? '',
Entrypoint: options.entrypoint,
WorkingDir: options.workingDir,
Env,
});
}

Просмотреть файл

@ -301,7 +301,7 @@ copyFiles.push({
// Babel doesn't touch JS files, so copy them manually.
// For example: diff_match_patch.js
copyFiles.push({
files: 'packages/playwright-core/src/**/*.(js|sh)',
files: 'packages/playwright-core/src/**/*.js',
from: 'packages/playwright-core/src',
to: 'packages/playwright-core/lib',
ignored: ['**/.eslintrc.js', '**/webpack*.config.js', '**/injected/**/*']