Skip to content

Commit

Permalink
name docker images
Browse files Browse the repository at this point in the history
  • Loading branch information
aslushnikov committed Sep 6, 2022
1 parent 0dd3ead commit 2e02eed
Showing 1 changed file with 103 additions and 70 deletions.
173 changes: 103 additions & 70 deletions packages/playwright-core/src/cli/docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,24 @@ interface DockerImage {
VirtualSize: number;
}

const VRT_UBUNTU_CODE_NAME = 'jammy';
const VRT_IMAGE_NAME = `mcr.microsoft.com/playwright:v${getPlaywrightVersion()}-${VRT_UBUNTU_CODE_NAME}-vrt`;
const VRT_IMAGE_DISTRO = 'jammy';
const VRT_IMAGE_NAME = `mcr.microsoft.com/playwright:v${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}-vrt`;
const VRT_CONTAINER_NAME = `playwright-${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}-vrt`;

const GENERATE_FLUXBOX_BROWSERS_MENU = `
const { chromium, firefox, webkit } = require('playwright-core');
console.log(\`
[begin] (fluxbox)
[submenu] (Browsers) {}
[exec] (Chromium) { $\{chromium.executablePath()} --no-sandbox } <>
[exec] (Firefox) { $\{firefox.executablePath()} } <>
[exec] (WebKit) { $\{webkit.executablePath()} } <>
[end]
[include] (/etc/X11/fluxbox/fluxbox-menu)
[end]
\`);
`;

const CONTAINER_ENTRY_POINT = `#!/bin/bash
set -e
Expand Down Expand Up @@ -67,29 +83,42 @@ const CONTAINER_ENTRY_POINT = `#!/bin/bash
npx playwright run-server --port=5400 --path=/$UUID
`;

const GENERATE_FLUXBOX_BROWSERS_MENU = `
const { chromium, firefox, webkit } = require('playwright-core');
const path = require('path');
const fs = require('fs');
fs.mkdirSync(path.join(process.env.HOME, '.fluxbox'), { recursive: true });
const menuFilePath = path.join(process.env.HOME, '.fluxbox', 'menu');
fs.writeFileSync(menuFilePath, \`
[begin] (fluxbox)
[submenu] (Browsers) {}
[exec] (Chromium) { $\{chromium.executablePath()} --no-sandbox } <>
[exec] (Firefox) { $\{firefox.executablePath()} } <>
[exec] (WebKit) { $\{webkit.executablePath()} } <>
[end]
[include] (/etc/X11/fluxbox/fluxbox-menu)
[end]
\`, 'utf-8');
`;
const NOVNC_REF = '1.3.0';
const WEBSOCKIFY_REF = '0.10.0';
const CONTAINER_BUILD_SCRIPT = `
# Generate entry point script
cat <<'EOF' >/start.sh
${CONTAINER_ENTRY_POINT}
EOF
chmod 755 /start.sh
export DEBIAN_FRONTEND=noninteractive
# Install FluxBox, VNC & noVNC
mkdir -p /opt/bin && chmod +x /dev/shm \
&& apt-get update && apt-get install -y unzip fluxbox x11vnc \
&& curl -L -o noVNC.zip "https://github.com/novnc/noVNC/archive/v${NOVNC_REF}.zip" \
&& unzip -x noVNC.zip \
&& mv noVNC-${NOVNC_REF} /opt/bin/noVNC \
&& cp /opt/bin/noVNC/vnc.html /opt/bin/noVNC/index.html \
&& rm noVNC.zip \
&& curl -L -o websockify.zip "https://github.com/novnc/websockify/archive/v${WEBSOCKIFY_REF}.zip" \
&& unzip -x websockify.zip \
&& rm websockify.zip \
&& mv websockify-${WEBSOCKIFY_REF} /opt/bin/noVNC/utils/websockify
# Configure FluxBox menus
cd /ms-playwright-agent
cat <<'EOF' | node > configuration.txt
${GENERATE_FLUXBOX_BROWSERS_MENU}
EOF
mkdir /root/.fluxbox && mv /ms-playwright-agent/configuration.txt /root/.fluxbox/menu
`.split('\n').map(line => line.substring(2)).join('\n');;

export async function buildVRTDockerImage() {
await ensureDockerIsRunning();
const isDevelopmentMode = getPlaywrightVersion().includes('next');
let baseImageName = `mcr.microsoft.com/playwright:v${getPlaywrightVersion()}-${VRT_UBUNTU_CODE_NAME}`;
let baseImageName = `mcr.microsoft.com/playwright:v${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}`;
// 1. Build or pull base image.
if (isDevelopmentMode) {
// Use our docker build scripts in development mode!
Expand All @@ -98,8 +127,8 @@ export async function buildVRTDockerImage() {
console.log(`You are in DEVELOPMENT mode!`);
console.log(``);
console.log(`To install docker image:`);
console.log(`1. Build base image with ./utils/docker/build.sh ${arch} jammy playwright:localbuild-jammy`);
console.log(`2. Use the base image to build VRT image: PWTEST_DOCKER_BASE_IMAGE=playwright:localbuild-jammy npx playwright docker install`);
console.log(`1. Build base image with ./utils/docker/build.sh ${arch} ${VRT_IMAGE_DISTRO} playwright:localbuild`);
console.log(`2. Use the base image to build VRT image: PWTEST_DOCKER_BASE_IMAGE=playwright:localbuild npx playwright docker install`);
console.log(``);
process.exit(1);
} else {
Expand All @@ -117,37 +146,12 @@ export async function buildVRTDockerImage() {
if (!dockerImage)
throw new Error(`Failed to pull ${baseImageName}`);
// 3. Launch container and install VNC in it
const NOVNC_REF = '1.3.0';
const WEBSOCKIFY_REF = '0.10.0';
console.log(`Building ${VRT_IMAGE_NAME}...`);
const containerId = await launchContainer(dockerImage, ['/bin/bash', '-c', `
# Generate entry point script
cat <<'EOF' >/start.sh
${CONTAINER_ENTRY_POINT}
EOF
chmod 755 /start.sh
export DEBIAN_FRONTEND=noninteractive
# Install FluxBox, VNC & noVNC
mkdir -p /opt/bin && chmod +x /dev/shm \
&& apt-get update && apt-get install -y unzip fluxbox x11vnc \
&& curl -L -o noVNC.zip "https://github.com/novnc/noVNC/archive/v${NOVNC_REF}.zip" \
&& unzip -x noVNC.zip \
&& mv noVNC-${NOVNC_REF} /opt/bin/noVNC \
&& cp /opt/bin/noVNC/vnc.html /opt/bin/noVNC/index.html \
&& rm noVNC.zip \
&& curl -L -o websockify.zip "https://github.com/novnc/websockify/archive/v${WEBSOCKIFY_REF}.zip" \
&& unzip -x websockify.zip \
&& rm websockify.zip \
&& mv websockify-${WEBSOCKIFY_REF} /opt/bin/noVNC/utils/websockify
# Configure FluxBox menus
cd /ms-playwright-agent
cat <<'EOF' | node
${GENERATE_FLUXBOX_BROWSERS_MENU}
EOF
`], false /* autoRemove */);
const containerId = await launchContainer({
image: dockerImage,
autoRemove: false,
command: ['/bin/bash', '-c', CONTAINER_BUILD_SCRIPT],
});
await postJSON(`/containers/${containerId}/wait`);

// 4. Commit a new image based on the launched container with installed VNC & noVNC.
Expand Down Expand Up @@ -184,7 +188,7 @@ export async function wsEndpoint(): Promise<string> {
}

async function wsEndpointInternal(): Promise<string|undefined> {
const containerId = await findRunningDockerContainerId(VRT_IMAGE_NAME);
const containerId = await findRunningDockerContainerId();
if (!containerId)
return undefined;
const rawLogs = await callDockerAPI('get', `/containers/${containerId}/logs?stdout=true&stderr=true`).catch(e => '');
Expand Down Expand Up @@ -221,7 +225,12 @@ export async function startVRTService() {
return;
}

await launchContainer(pwImage, [], true /* autoRemove */, [5400, 7900] /* portForwarding */);
await launchContainer({
image: pwImage,
name: VRT_CONTAINER_NAME,
autoRemove: true,
ports: [5400, 7900],
});

// Wait for the service to become available.
const startTime = Date.now();
Expand All @@ -247,7 +256,7 @@ export async function startVRTService() {

export async function stopVRTService() {
await ensureDockerIsRunning();
const containerId = await findRunningDockerContainerId(VRT_IMAGE_NAME);
const containerId = await findRunningDockerContainerId();
if (!containerId) {
console.log(`Container is not running.`);
return undefined;
Expand Down Expand Up @@ -278,30 +287,53 @@ async function findDockerImage(imageName: string): Promise<DockerImage|undefined
return images ? images.find(image => image.RepoTags?.includes(imageName)) : undefined;
}

async function findRunningDockerContainerId(imageName: string): Promise<string|undefined> {
const dockerImage = await findDockerImage(imageName);
if (!dockerImage)
interface Container {
ImageID: string;
State: string;
Name: string;
Id: string;
};

async function findRunningDockerContainerId(): Promise<string|undefined> {
const containers: (Container[]|undefined) = await getJSON('/containers/json');
if (!containers)
return undefined;
const containers = await getJSON('/containers/json');
return containers?.find((container: any) => container.ImageID === dockerImage.Id && container.State === 'running')?.Id;
// 1. Try findind a container with our name. This happens if the container is launched
// automatically with `npx playwright docker start`.
let container = containers.find((container: Container) => container.Name === VRT_CONTAINER_NAME);
// 2. Alternatively, the container might be launched manually with a direct docker command.
// In this case, we should look for a container with a proper base image.
if (!container) {
const dockerImage = await findDockerImage(VRT_IMAGE_NAME);
container = dockerImage ? containers.find((container: Container) => container.ImageID === dockerImage.Id) : undefined;
}
return container?.State === 'running' ? container.Id : undefined;
}

async function launchContainer(dockerImage: DockerImage, command: string[], autoRemove: boolean, portsToForward: Number[] = []): Promise<string> {
interface ContainerOptions {
image: DockerImage;
autoRemove: boolean;
command?: string[];
ports?: Number[];
name?: string;
};

async function launchContainer(options: ContainerOptions): Promise<string> {
const ExposedPorts: any = {};
const PortBindings: any = {};
for (const port of portsToForward) {
for (const port of (options.ports ?? [])) {
ExposedPorts[`${port}/tcp`] = {};
PortBindings[`${port}/tcp`] = [{ HostPort: port + '' }];
}
const container = await postJSON(`/containers/create`, {
Cmd: command,
const container = await postJSON(`/containers/create` + (options.name ? '?name=' + options.name : ''), {
Cmd: options.command,
AttachStdout: true,
AttachStderr: true,
Image: dockerImage.Id,
Image: options.image.Id,
ExposedPorts,
HostConfig: {
Init: true,
AutoRemove: autoRemove,
AutoRemove: options.autoRemove,
ShmSize: 2 * 1024 * 1024 * 1024,
PortBindings,
},
Expand All @@ -324,13 +356,14 @@ async function postJSON(url: string, json: any = undefined) {
return JSON.parse(result);
}

const DOCKER_API_VERSION = '1.41';

function callDockerAPI(method: 'post'|'get'|'delete', url: string, body: Buffer|string|undefined = undefined): Promise<string> {
const dockerSocket = process.platform === 'win32' ? '\\\\.\\pipe\\docker_engine' : '/var/run/docker.sock';
const API_VERSION = '1.41';
return new Promise((resolve, reject) => {
const request = http.request({
socketPath: dockerSocket,
path: `/v${API_VERSION}${url}`,
path: `/v${DOCKER_API_VERSION}${url}`,
method,
}, (response: http.IncomingMessage) => {
let body = '';
Expand All @@ -342,7 +375,6 @@ function callDockerAPI(method: 'post'|'get'|'delete', url: string, body: Buffer|
reject(new Error(`${method} ${url} FAILED with statusCode ${response.statusCode} and body\n${body}`));
else
resolve(body);

});
});
request.on('error', function(e){
Expand All @@ -358,3 +390,4 @@ function callDockerAPI(method: 'post'|'get'|'delete', url: string, body: Buffer|
request.end();
});
}

0 comments on commit 2e02eed

Please sign in to comment.