Update docker related dependencies

This bumps dockerode, removes resin-docker-build in favor of
@balena/compose, and updates docker-delta and docker-progress packages.

Change-type: patch
This commit is contained in:
Felipe Lalanne 2024-04-03 17:56:12 -03:00
parent 15bccbf6b3
commit ae823fea18
10 changed files with 844 additions and 1403 deletions

2089
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -39,6 +39,7 @@
"npm": ">=10" "npm": ">=10"
}, },
"devDependencies": { "devDependencies": {
"@balena/compose": "^3.2.1",
"@balena/contrato": "^0.6.0", "@balena/contrato": "^0.6.0",
"@balena/es-version": "^1.0.3", "@balena/es-version": "^1.0.3",
"@balena/lint": "^8.0.2", "@balena/lint": "^8.0.2",
@ -48,7 +49,7 @@
"@types/chai-like": "^1.1.1", "@types/chai-like": "^1.1.1",
"@types/chai-things": "0.0.38", "@types/chai-things": "0.0.38",
"@types/common-tags": "^1.8.1", "@types/common-tags": "^1.8.1",
"@types/dockerode": "^2.5.34", "@types/dockerode": "^3.3.28",
"@types/event-stream": "^3.3.34", "@types/event-stream": "^3.3.34",
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
"@types/json-mask": "^2.0.3", "@types/json-mask": "^2.0.3",
@ -66,6 +67,7 @@
"@types/sinon": "^17.0.3", "@types/sinon": "^17.0.3",
"@types/sinon-chai": "^3.2.12", "@types/sinon-chai": "^3.2.12",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"@types/tar-stream": "^3.1.3",
"@types/webpack": "^5.28.0", "@types/webpack": "^5.28.0",
"@types/yargs": "^17.0.32", "@types/yargs": "^17.0.32",
"balena-auth": "^6.0.1", "balena-auth": "^6.0.1",
@ -81,9 +83,9 @@
"common-tags": "^1.8.0", "common-tags": "^1.8.0",
"copy-webpack-plugin": "^12.0.0", "copy-webpack-plugin": "^12.0.0",
"deep-object-diff": "^1.1.0", "deep-object-diff": "^1.1.0",
"docker-delta": "^2.2.11", "docker-delta": "^4.0.1",
"docker-progress": "^4.0.3", "docker-progress": "^5.2.3",
"dockerode": "^2.5.8", "dockerode": "^4.0.2",
"duration-js": "^4.0.0", "duration-js": "^4.0.0",
"event-stream": "3.3.5", "event-stream": "3.3.5",
"express": "^4.17.1", "express": "^4.17.1",
@ -106,11 +108,11 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"network-checker": "^0.1.1", "network-checker": "^0.1.1",
"nock": "^13.1.2", "nock": "^13.1.2",
"node-loader": "^2.0.0",
"nodemon": "^3.1.0", "nodemon": "^3.1.0",
"pinejs-client-request": "^7.3.5", "pinejs-client-request": "^7.3.5",
"pretty-ms": "^7.0.1", "pretty-ms": "^7.0.1",
"request": "^2.88.2", "request": "^2.88.2",
"resin-docker-build": "^1.1.6",
"resumable-request": "^2.0.1", "resumable-request": "^2.0.1",
"rewire": "^7.0.0", "rewire": "^7.0.0",
"rimraf": "^5.0.0", "rimraf": "^5.0.0",

View File

@ -506,7 +506,14 @@ export class Service {
const ulimits: ServiceConfig['ulimits'] = {}; const ulimits: ServiceConfig['ulimits'] = {};
_.each(container.HostConfig.Ulimits, ({ Name, Soft, Hard }) => { _.each(container.HostConfig.Ulimits, ({ Name, Soft, Hard }) => {
ulimits[Name] = { soft: Soft, hard: Hard }; // The Ulimit type in @types/dockerode allows any element to be
// null which is probably wrong
if (Name != null && Soft != null && Hard != null) {
ulimits[Name] = {
soft: Soft,
hard: Hard,
};
}
}); });
const portMaps = PortMap.fromDockerOpts(container.HostConfig.PortBindings); const portMaps = PortMap.fromDockerOpts(container.HostConfig.PortBindings);

View File

@ -97,7 +97,7 @@ export class Volume {
Name: Volume.generateDockerName(this.appId, this.name), Name: Volume.generateDockerName(this.appId, this.name),
Labels: this.config.labels, Labels: this.config.labels,
Driver: this.config.driver, Driver: this.config.driver,
DriverOpts: this.config.driverOpts, DriverOpts: this.config.driverOpts!,
}); });
} }

View File

@ -5,7 +5,6 @@ import _ from 'lodash';
import memoizee from 'memoizee'; import memoizee from 'memoizee';
import { applyDelta, OutOfSyncError } from 'docker-delta'; import { applyDelta, OutOfSyncError } from 'docker-delta';
import DockerToolbelt = require('docker-toolbelt');
import type { SchemaReturn } from '../config/schema-type'; import type { SchemaReturn } from '../config/schema-type';
import { envArrayToObject } from './conversions'; import { envArrayToObject } from './conversions';
@ -43,9 +42,8 @@ type ImageNameParts = {
const DELTA_TOKEN_TIMEOUT = 10 * 60 * 1000; const DELTA_TOKEN_TIMEOUT = 10 * 60 * 1000;
export const docker = new Dockerode(); export const docker = new Dockerode();
export const dockerToolbelt = new DockerToolbelt(undefined);
export const dockerProgress = new DockerProgress({ export const dockerProgress = new DockerProgress({
dockerToolbelt, docker,
}); });
// Separate string containing registry and image name into its parts. // Separate string containing registry and image name into its parts.
@ -153,10 +151,10 @@ export async function fetchDeltaWithProgress(
logFn(`Starting delta to ${imgDest}`); logFn(`Starting delta to ${imgDest}`);
const [dstInfo, srcInfo] = await Promise.all([ const [dstInfo, srcInfo] = [
dockerToolbelt.getRegistryAndName(imgDest), getRegistryAndName(imgDest),
dockerToolbelt.getRegistryAndName(deltaOpts.deltaSource), getRegistryAndName(deltaOpts.deltaSource),
]); ];
const token = await getAuthToken(srcInfo, dstInfo, deltaOpts); const token = await getAuthToken(srcInfo, dstInfo, deltaOpts);
@ -252,7 +250,7 @@ export async function fetchImageWithProgress(
{ uuid, currentApiKey }: FetchOptions, { uuid, currentApiKey }: FetchOptions,
onProgress: ProgressCallback, onProgress: ProgressCallback,
): Promise<string> { ): Promise<string> {
const { registry } = await dockerToolbelt.getRegistryAndName(image); const { registry } = getRegistryAndName(image);
const dockerOpts = const dockerOpts =
// If no registry is specified, we assume the image is a public // If no registry is specified, we assume the image is a public

View File

@ -1,6 +1,6 @@
import Docker from 'dockerode'; import Docker from 'dockerode';
import type { Dockerfile } from 'livepush'; import type { Dockerfile } from 'livepush';
import { Builder } from 'resin-docker-build'; import { build } from '@balena/compose';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import * as Path from 'path'; import * as Path from 'path';
@ -10,6 +10,8 @@ import * as readline from 'readline';
import { exec } from '../src/lib/fs-utils'; import { exec } from '../src/lib/fs-utils';
const { Builder } = build;
export function getDocker(deviceAddress: string): Docker { export function getDocker(deviceAddress: string): Docker {
return new Docker({ return new Docker({
host: deviceAddress, host: deviceAddress,

View File

@ -88,7 +88,10 @@ describe('LocalModeManager', () => {
}), }),
); );
dockerStub.listNetworks.returns( dockerStub.listNetworks.returns(
Promise.resolve([{ Id: 'network-1' }, { Id: 'network-2' }]), Promise.resolve([
{ Id: 'network-1' },
{ Id: 'network-2' },
] as Docker.NetworkInspectInfo[]),
); );
}); });

View File

@ -1,8 +1,8 @@
process.env.DOCKER_HOST = 'unix:///your/dockerode/mocks/are/not/working'; process.env.DOCKER_HOST = 'unix:///your/dockerode/mocks/are/not/working';
import * as dockerode from 'dockerode'; import Dockerode from 'dockerode';
import { Stream } from 'stream'; import { Stream } from 'stream';
import _ = require('lodash'); import _ from 'lodash';
import { NotFoundError } from '~/lib/errors'; import { NotFoundError } from '~/lib/errors';
const overrides: Dictionary<(...args: any[]) => Resolvable<any>> = {}; const overrides: Dictionary<(...args: any[]) => Resolvable<any>> = {};
@ -30,13 +30,13 @@ function addAction(name: string, parameters: Dictionary<any> = {}) {
}); });
} }
type DockerodeFunction = keyof dockerode; type DockerodeFunction = keyof Omit<Dockerode, 'modem'>;
for (const fn of Object.getOwnPropertyNames(dockerode.prototype)) { for (const fn of Object.getOwnPropertyNames(Dockerode.prototype)) {
if ( if (
fn !== 'constructor' && fn !== 'constructor' &&
typeof (dockerode.prototype as any)[fn] === 'function' typeof (Dockerode.prototype as any)[fn] === 'function'
) { ) {
(dockerode.prototype as any)[fn] = async function (...args: any[]) { (Dockerode.prototype as any)[fn] = async function (...args: any[]) {
console.log(`🐳 Calling ${fn}...`); console.log(`🐳 Calling ${fn}...`);
if (overrides[fn] != null) { if (overrides[fn] != null) {
return overrides[fn](args); return overrides[fn](args);
@ -66,8 +66,8 @@ registerOverride(
*/ */
export function registerOverride< export function registerOverride<
T extends DockerodeFunction, T extends DockerodeFunction,
P extends Parameters<dockerode[T]>, P extends Parameters<Dockerode[T]>,
R extends ReturnType<dockerode[T]>, R extends ReturnType<Dockerode[T]>,
>(name: T, fn: (...args: P) => R) { >(name: T, fn: (...args: P) => R) {
console.log(`Overriding ${name}...`); console.log(`Overriding ${name}...`);
overrides[name] = fn; overrides[name] = fn;
@ -87,14 +87,14 @@ export interface TestData {
} }
function createMockedDockerode(data: TestData) { function createMockedDockerode(data: TestData) {
const mockedDockerode = dockerode.prototype; const mockedDockerode = Dockerode.prototype;
mockedDockerode.listImages = async () => []; mockedDockerode.listImages = async () => [];
mockedDockerode.listVolumes = async () => { mockedDockerode.listVolumes = async () => {
addAction('listVolumes'); addAction('listVolumes');
return { return {
Volumes: data.volumes as dockerode.VolumeInspectInfo[], Volumes: data.volumes as Dockerode.VolumeInspectInfo[],
Warnings: [], Warnings: [],
}; };
}; };
@ -120,18 +120,18 @@ function createMockedDockerode(data: TestData) {
}, },
name: volume.name, name: volume.name,
modem: {}, modem: {},
} as dockerode.Volume; } as Dockerode.Volume;
}; };
mockedDockerode.createContainer = async ( mockedDockerode.createContainer = async (
options: dockerode.ContainerCreateOptions, options: Dockerode.ContainerCreateOptions,
) => { ) => {
addAction('createContainer', { options }); addAction('createContainer', { options });
return { return {
start: async () => { start: async () => {
addAction('start'); addAction('start');
}, },
} as dockerode.Container; } as Dockerode.Container;
}; };
mockedDockerode.getContainer = (id: string) => { mockedDockerode.getContainer = (id: string) => {
@ -167,7 +167,7 @@ function createMockedDockerode(data: TestData) {
return c; return c;
}); });
}, },
} as dockerode.Container; } as Dockerode.Container;
}; };
mockedDockerode.getNetwork = (id: string) => { mockedDockerode.getNetwork = (id: string) => {
@ -177,7 +177,7 @@ function createMockedDockerode(data: TestData) {
addAction('inspect'); addAction('inspect');
return data.networks[id]; return data.networks[id];
}, },
} as dockerode.Network; } as Dockerode.Network;
}; };
mockedDockerode.getImage = (name: string) => { mockedDockerode.getImage = (name: string) => {
@ -193,7 +193,7 @@ function createMockedDockerode(data: TestData) {
name, name,
}); });
}, },
} as dockerode.Image; } as Dockerode.Image;
}; };
return mockedDockerode; return mockedDockerode;
@ -232,16 +232,14 @@ export async function testWithData(
}; };
// grab the original prototype... // grab the original prototype...
const basePrototype = clonePrototype(dockerode.prototype); const basePrototype = clonePrototype(Dockerode.prototype);
Dockerode.prototype = createMockedDockerode(mockedData);
// @ts-expect-error setting a RO property
dockerode.prototype = createMockedDockerode(mockedData);
try { try {
// run the test... // run the test...
await test(); await test();
} finally { } finally {
// reset the original prototype... // reset the original prototype...
assignPrototype(dockerode.prototype, basePrototype); assignPrototype(Dockerode.prototype, basePrototype);
} }
} }

View File

@ -1,4 +1,4 @@
import dockerode from 'dockerode'; import Dockerode from 'dockerode';
import sinon from 'sinon'; import sinon from 'sinon';
import { randomUUID as uuidv4 } from 'crypto'; import { randomUUID as uuidv4 } from 'crypto';
@ -14,31 +14,31 @@ type DeepPartial<T> = {
// Partial container inspect info for receiving as testing data // Partial container inspect info for receiving as testing data
export type PartialContainerInspectInfo = export type PartialContainerInspectInfo =
DeepPartial<dockerode.ContainerInspectInfo> & { DeepPartial<Dockerode.ContainerInspectInfo> & {
Id: string; Id: string;
}; };
export type PartialNetworkInspectInfo = export type PartialNetworkInspectInfo =
DeepPartial<dockerode.NetworkInspectInfo> & { DeepPartial<Dockerode.NetworkInspectInfo> & {
Id: string; Id: string;
}; };
export type PartialVolumeInspectInfo = export type PartialVolumeInspectInfo =
DeepPartial<dockerode.VolumeInspectInfo> & { DeepPartial<Dockerode.VolumeInspectInfo> & {
Name: string; Name: string;
}; };
export type PartialImageInspectInfo = export type PartialImageInspectInfo =
DeepPartial<dockerode.ImageInspectInfo> & { DeepPartial<Dockerode.ImageInspectInfo> & {
Id: string; Id: string;
}; };
type Methods<T> = { type Fake<T> = {
[K in keyof T]: T[K] extends (...args: any) => any ? T[K] : never; [K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : never;
}; };
function createFake<Prototype extends object>(prototype: Prototype) { function createFake<T extends object>(prototype: T): Fake<T> {
return (Object.getOwnPropertyNames(prototype) as Array<keyof Prototype>) return (Object.getOwnPropertyNames(prototype) as Array<keyof T>)
.filter((fn) => fn === 'constructor' || typeof prototype[fn] === 'function') .filter((fn) => fn === 'constructor' || typeof prototype[fn] === 'function')
.reduce( .reduce(
(res, fn) => ({ (res, fn) => ({
@ -51,7 +51,7 @@ function createFake<Prototype extends object>(prototype: Prototype) {
); );
}, },
}), }),
{} as Methods<Prototype>, {} as Fake<T>,
); );
} }
@ -86,7 +86,7 @@ export function createNetwork(network: PartialNetworkInspectInfo) {
...networkInspect, ...networkInspect,
}; };
const fakeNetwork = createFake(dockerode.Network.prototype); const fakeNetwork = createFake(Dockerode.Network.prototype);
return { return {
...fakeNetwork, // by default all methods fail unless overriden ...fakeNetwork, // by default all methods fail unless overriden
@ -103,7 +103,7 @@ export type MockNetwork = ReturnType<typeof createNetwork>;
export function createContainer(container: PartialContainerInspectInfo) { export function createContainer(container: PartialContainerInspectInfo) {
const createContainerInspectInfo = ( const createContainerInspectInfo = (
partial: PartialContainerInspectInfo, partial: PartialContainerInspectInfo,
): dockerode.ContainerInspectInfo => { ): Dockerode.ContainerInspectInfo => {
const { const {
Id, Id,
State, State,
@ -200,12 +200,12 @@ export function createContainer(container: PartialContainerInspectInfo) {
], ],
...ContainerInfo, ...ContainerInfo,
} as dockerode.ContainerInspectInfo; } as Dockerode.ContainerInspectInfo;
}; };
const createContainerInfo = ( const createContainerInfo = (
containerInspectInfo: dockerode.ContainerInspectInfo, containerInspectInfo: Dockerode.ContainerInspectInfo,
): dockerode.ContainerInfo => { ): Dockerode.ContainerInfo => {
const { const {
Id, Id,
Name, Name,
@ -239,7 +239,7 @@ export function createContainer(container: PartialContainerInspectInfo) {
NetworkSettings: { NetworkSettings: {
Networks: NetworkSettings.Networks, Networks: NetworkSettings.Networks,
}, },
Mounts: Mounts as dockerode.ContainerInfo['Mounts'], Mounts: Mounts as Dockerode.ContainerInfo['Mounts'],
}; };
}; };
@ -248,7 +248,7 @@ export function createContainer(container: PartialContainerInspectInfo) {
const { Id: id } = inspectInfo; const { Id: id } = inspectInfo;
const fakeContainer = createFake(dockerode.Container.prototype); const fakeContainer = createFake(Dockerode.Container.prototype);
return { return {
...fakeContainer, // by default all methods fail unless overriden ...fakeContainer, // by default all methods fail unless overriden
@ -318,7 +318,7 @@ export function createImage(
) { ) {
const createImageInspectInfo = ( const createImageInspectInfo = (
partialImage: PartialImageInspectInfo, partialImage: PartialImageInspectInfo,
): dockerode.ImageInspectInfo => { ): Dockerode.ImageInspectInfo => {
const { Id, ContainerConfig, Config, GraphDriver, RootFS, ...Info } = const { Id, ContainerConfig, Config, GraphDriver, RootFS, ...Info } =
partialImage; partialImage;
@ -357,7 +357,7 @@ export function createImage(
Labels: {}, Labels: {},
...ContainerConfig, ...ContainerConfig,
} as dockerode.ImageInspectInfo['ContainerConfig'], } as Dockerode.ImageInspectInfo['ContainerConfig'],
DockerVersion: '17.05.0-ce', DockerVersion: '17.05.0-ce',
Author: '', Author: '',
Config: { Config: {
@ -385,7 +385,7 @@ export function createImage(
...(Config?.Labels ?? {}), ...(Config?.Labels ?? {}),
}, },
...Config, ...Config,
} as dockerode.ImageInspectInfo['Config'], } as Dockerode.ImageInspectInfo['Config'],
Architecture: 'arm64', Architecture: 'arm64',
Os: 'linux', Os: 'linux',
@ -400,7 +400,7 @@ export function createImage(
Name: 'aufs', Name: 'aufs',
...GraphDriver, ...GraphDriver,
} as dockerode.ImageInspectInfo['GraphDriver'], } as Dockerode.ImageInspectInfo['GraphDriver'],
RootFS: { RootFS: {
Type: 'layers', Type: 'layers',
Layers: [ Layers: [
@ -410,13 +410,13 @@ export function createImage(
], ],
...RootFS, ...RootFS,
} as dockerode.ImageInspectInfo['RootFS'], } as Dockerode.ImageInspectInfo['RootFS'],
...Info, ...Info,
}; };
}; };
const createImageInfo = (imageInspectInfo: dockerode.ImageInspectInfo) => { const createImageInfo = (imageInspectInfo: Dockerode.ImageInspectInfo) => {
const { const {
Id, Id,
Parent: ParentId, Parent: ParentId,
@ -470,7 +470,7 @@ export function createImage(
const info = createImageInfo(inspectInfo); const info = createImageInfo(inspectInfo);
const { Id: id } = inspectInfo; const { Id: id } = inspectInfo;
const fakeImage = createFake(dockerode.Image.prototype); const fakeImage = createFake(Dockerode.Image.prototype);
return { return {
...fakeImage, // by default all methods fail unless overriden ...fakeImage, // by default all methods fail unless overriden
@ -492,20 +492,20 @@ export type MockImage = ReturnType<typeof createImage>;
export function createVolume(volume: PartialVolumeInspectInfo) { export function createVolume(volume: PartialVolumeInspectInfo) {
const { Name, Labels, ...partialVolumeInfo } = volume; const { Name, Labels, ...partialVolumeInfo } = volume;
const inspectInfo: dockerode.VolumeInspectInfo = { const inspectInfo: Dockerode.VolumeInspectInfo = {
Name, Name,
Driver: 'local', Driver: 'local',
Mountpoint: '/var/lib/docker/volumes/resin-data', Mountpoint: '/var/lib/docker/volumes/resin-data',
Labels: { Labels: {
...Labels, ...Labels,
} as dockerode.VolumeInspectInfo['Labels'], } as Dockerode.VolumeInspectInfo['Labels'],
Scope: 'local', Scope: 'local',
Options: {}, Options: {},
...partialVolumeInfo, ...partialVolumeInfo,
}; };
const fakeVolume = createFake(dockerode.Volume.prototype); const fakeVolume = createFake(Dockerode.Volume.prototype);
return { return {
...fakeVolume, // by default all methods fail unless overriden ...fakeVolume, // by default all methods fail unless overriden
name: Name, name: Name,
@ -630,7 +630,7 @@ export class MockEngine {
return Promise.resolve(delete this.networks[network.id]); return Promise.resolve(delete this.networks[network.id]);
} }
createNetwork(options: dockerode.NetworkCreateOptions) { createNetwork(options: Dockerode.NetworkCreateOptions) {
const Id = uuidv4(); const Id = uuidv4();
const network = createNetwork({ Id, ...options }); const network = createNetwork({ Id, ...options });
@ -645,7 +645,7 @@ export class MockEngine {
); );
} }
createContainer(options: dockerode.ContainerCreateOptions) { createContainer(options: Dockerode.ContainerCreateOptions) {
const Id = uuidv4(); const Id = uuidv4();
const { name: Name, HostConfig, NetworkingConfig, ...Config } = options; const { name: Name, HostConfig, NetworkingConfig, ...Config } = options;
@ -890,12 +890,12 @@ export class MockEngine {
} }
export function createMockerode(engine: MockEngine) { export function createMockerode(engine: MockEngine) {
const dockerodeStubs: Stubs<dockerode> = ( const dockerodeStubs: Stubs<Dockerode> = (
Object.getOwnPropertyNames(dockerode.prototype) as Array<keyof dockerode> Object.getOwnPropertyNames(Dockerode.prototype) as Array<keyof Dockerode>
) )
.filter((fn) => typeof dockerode.prototype[fn] === 'function') .filter((fn) => typeof Dockerode.prototype[fn] === 'function')
.reduce((stubMap, fn) => { .reduce((stubMap, fn) => {
const stub = sinon.stub(dockerode.prototype, fn); const stub = sinon.stub(Dockerode.prototype, fn);
const proto: any = MockEngine.prototype; const proto: any = MockEngine.prototype;
if (fn in proto) { if (fn in proto) {
@ -907,7 +907,7 @@ export function createMockerode(engine: MockEngine) {
} }
return { ...stubMap, [fn]: stub }; return { ...stubMap, [fn]: stub };
}, {} as Stubs<dockerode>); }, {} as Stubs<Dockerode>);
const { removeImage, removeNetwork, removeVolume, removeContainer } = engine; const { removeImage, removeNetwork, removeVolume, removeContainer } = engine;

View File

@ -131,6 +131,10 @@ module.exports = function (env) {
}, },
], ],
}, },
{
test: /\.node$/,
loader: 'node-loader',
},
], ],
}, },
externals: (_context, request, callback) => { externals: (_context, request, callback) => {