mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-30 02:28:53 +00:00
Fix livepush to work with node 16
This also improves the memory efficiency of the sync mechanism by calculating the stage ids on the fly instead of storing the full build output in memory and then parsing the string.
This commit is contained in:
parent
b168cc35a0
commit
b207c01988
@ -5,8 +5,9 @@ import { Builder } from 'resin-docker-build';
|
|||||||
|
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import * as Path from 'path';
|
import * as Path from 'path';
|
||||||
import { Duplex, Readable, PassThrough, Stream } from 'stream';
|
import { Readable } from 'stream';
|
||||||
import * as tar from 'tar-stream';
|
import * as tar from 'tar-stream';
|
||||||
|
import * as readline from 'readline';
|
||||||
|
|
||||||
import { exec } from '../src/lib/fs-utils';
|
import { exec } from '../src/lib/fs-utils';
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ export async function getDeviceArch(docker: Docker): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return arch.trim();
|
return arch.trim();
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unable to get device architecture: ${e.message}.\nTry specifying the architecture with -a.`,
|
`Unable to get device architecture: ${e.message}.\nTry specifying the architecture with -a.`,
|
||||||
);
|
);
|
||||||
@ -68,31 +69,61 @@ export async function getCacheFrom(docker: Docker): Promise<string[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Source: https://github.com/balena-io/balena-cli/blob/f6d668684a6f5ea8102a964ca1942b242eaa7ae2/lib/utils/device/live.ts#L539-L547
|
||||||
|
function extractDockerArrowMessage(outputLine: string): string | undefined {
|
||||||
|
const arrowTest = /^.*\s*-+>\s*(.+)/i;
|
||||||
|
const match = arrowTest.exec(outputLine);
|
||||||
|
if (match != null) {
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function performBuild(
|
export async function performBuild(
|
||||||
docker: Docker,
|
docker: Docker,
|
||||||
dockerfile: Dockerfile,
|
dockerfile: Dockerfile,
|
||||||
dockerOpts: { [key: string]: any },
|
dockerOpts: { [key: string]: any },
|
||||||
): Promise<string> {
|
): Promise<string[]> {
|
||||||
const builder = Builder.fromDockerode(docker);
|
const builder = Builder.fromDockerode(docker);
|
||||||
|
|
||||||
// tar the directory, but replace the dockerfile with the
|
// tar the directory, but replace the dockerfile with the
|
||||||
// livepush generated one
|
// livepush generated one
|
||||||
const tarStream = await tarDirectory(Path.join(__dirname, '..'), dockerfile);
|
const tarStream = await tarDirectory(Path.join(__dirname, '..'), dockerfile);
|
||||||
const bufStream = new PassThrough();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const chunks = [] as Buffer[];
|
// Store the stage ids for caching
|
||||||
bufStream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
const ids = [] as string[];
|
||||||
builder.createBuildStream(dockerOpts, {
|
builder.createBuildStream(dockerOpts, {
|
||||||
buildSuccess: () => {
|
buildSuccess: () => {
|
||||||
// Return the build logs
|
// Return the image ids
|
||||||
resolve(Buffer.concat(chunks).toString('utf8'));
|
resolve(ids);
|
||||||
},
|
},
|
||||||
buildFailure: reject,
|
buildFailure: reject,
|
||||||
buildStream: (stream: Duplex) => {
|
buildStream: (input: NodeJS.ReadWriteStream) => {
|
||||||
stream.pipe(process.stdout);
|
// Parse the build output to get stage ids and
|
||||||
stream.pipe(bufStream);
|
// for logging
|
||||||
tarStream.pipe(stream);
|
let lastArrowMessage: string | undefined;
|
||||||
|
readline.createInterface({ input }).on('line', (line) => {
|
||||||
|
// If this was a FROM line, take the last found
|
||||||
|
// image id and save it as a stage id
|
||||||
|
// Source: https://github.com/balena-io/balena-cli/blob/f6d668684a6f5ea8102a964ca1942b242eaa7ae2/lib/utils/device/live.ts#L300-L325
|
||||||
|
if (
|
||||||
|
/step \d+(?:\/\d+)?\s*:\s*FROM/i.test(line) &&
|
||||||
|
lastArrowMessage != null
|
||||||
|
) {
|
||||||
|
ids.push(lastArrowMessage);
|
||||||
|
} else {
|
||||||
|
const msg = extractDockerArrowMessage(line);
|
||||||
|
if (msg != null) {
|
||||||
|
lastArrowMessage = msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the build line
|
||||||
|
console.info(line);
|
||||||
|
});
|
||||||
|
|
||||||
|
// stream.pipe(bufStream);
|
||||||
|
tarStream.pipe(input);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
37
sync/init.ts
37
sync/init.ts
@ -14,39 +14,6 @@ interface Opts {
|
|||||||
arch?: string;
|
arch?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source: https://github.com/balena-io/balena-cli/blob/f6d668684a6f5ea8102a964ca1942b242eaa7ae2/lib/utils/device/live.ts#L539-L547
|
|
||||||
function extractDockerArrowMessage(outputLine: string): string | undefined {
|
|
||||||
const arrowTest = /^.*\s*-+>\s*(.+)/i;
|
|
||||||
const match = arrowTest.exec(outputLine);
|
|
||||||
if (match != null) {
|
|
||||||
return match[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Source: https://github.com/balena-io/balena-cli/blob/f6d668684a6f5ea8102a964ca1942b242eaa7ae2/lib/utils/device/live.ts#L300-L325
|
|
||||||
function getMultiStateImageIDs(buildLog: string): string[] {
|
|
||||||
const ids = [] as string[];
|
|
||||||
const lines = buildLog.split(/\r?\n/);
|
|
||||||
let lastArrowMessage: string | undefined;
|
|
||||||
for (const line of lines) {
|
|
||||||
// If this was a from line, take the last found
|
|
||||||
// image id and save it
|
|
||||||
if (
|
|
||||||
/step \d+(?:\/\d+)?\s*:\s*FROM/i.test(line) &&
|
|
||||||
lastArrowMessage != null
|
|
||||||
) {
|
|
||||||
ids.push(lastArrowMessage);
|
|
||||||
} else {
|
|
||||||
const msg = extractDockerArrowMessage(line);
|
|
||||||
if (msg != null) {
|
|
||||||
lastArrowMessage = msg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPathPrefix(arch: string) {
|
function getPathPrefix(arch: string) {
|
||||||
switch (arch) {
|
switch (arch) {
|
||||||
/**
|
/**
|
||||||
@ -74,7 +41,7 @@ export async function initDevice(opts: Opts) {
|
|||||||
|
|
||||||
const buildCache = await device.readBuildCache(opts.address);
|
const buildCache = await device.readBuildCache(opts.address);
|
||||||
|
|
||||||
const buildLog = await device.performBuild(opts.docker, opts.dockerfile, {
|
const stageImages = await device.performBuild(opts.docker, opts.dockerfile, {
|
||||||
buildargs: { ARCH: arch, PREFIX: getPathPrefix(arch) },
|
buildargs: { ARCH: arch, PREFIX: getPathPrefix(arch) },
|
||||||
t: image,
|
t: image,
|
||||||
labels: { 'io.balena.livepush-image': '1', 'io.balena.architecture': arch },
|
labels: { 'io.balena.livepush-image': '1', 'io.balena.architecture': arch },
|
||||||
@ -84,8 +51,6 @@ export async function initDevice(opts: Opts) {
|
|||||||
nocache: opts.nocache,
|
nocache: opts.nocache,
|
||||||
});
|
});
|
||||||
|
|
||||||
const stageImages = getMultiStateImageIDs(buildLog);
|
|
||||||
|
|
||||||
// Store the list of stage images for the next time the sync
|
// Store the list of stage images for the next time the sync
|
||||||
// command is called. This will only live until the device is rebooted
|
// command is called. This will only live until the device is rebooted
|
||||||
await device.writeBuildCache(opts.address, stageImages);
|
await device.writeBuildCache(opts.address, stageImages);
|
||||||
|
@ -93,12 +93,12 @@ const argv = yargs
|
|||||||
sigint = () => reject(new Error('User interrupt (Ctrl+C) received'));
|
sigint = () => reject(new Error('User interrupt (Ctrl+C) received'));
|
||||||
process.on('SIGINT', sigint);
|
process.on('SIGINT', sigint);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
console.error('Error:', e.message);
|
console.error('Error:', e.message);
|
||||||
} finally {
|
} finally {
|
||||||
console.info('Cleaning up. Please wait ...');
|
console.info('Cleaning up. Please wait ...');
|
||||||
await cleanup();
|
await cleanup();
|
||||||
process.removeListener('SIGINT', sigint);
|
process.removeListener('SIGINT', sigint);
|
||||||
process.exit(1);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
Loading…
Reference in New Issue
Block a user