2020-07-21 15:25:47 +00:00
|
|
|
process.env.DOCKER_HOST = 'unix:///your/dockerode/mocks/are/not/working';
|
|
|
|
|
2024-04-03 20:56:12 +00:00
|
|
|
import Dockerode from 'dockerode';
|
2020-08-13 12:25:39 +00:00
|
|
|
import { Stream } from 'stream';
|
2024-04-03 20:56:12 +00:00
|
|
|
import _ from 'lodash';
|
2022-11-08 23:41:52 +00:00
|
|
|
import { NotFoundError } from '~/lib/errors';
|
2020-08-13 12:25:39 +00:00
|
|
|
|
|
|
|
const overrides: Dictionary<(...args: any[]) => Resolvable<any>> = {};
|
|
|
|
|
2020-10-30 21:38:19 +00:00
|
|
|
interface Action {
|
|
|
|
name: string;
|
|
|
|
parameters: Dictionary<any>;
|
|
|
|
}
|
|
|
|
|
|
|
|
export let actions: Action[] = [];
|
|
|
|
|
|
|
|
export function resetHistory() {
|
|
|
|
actions = [];
|
|
|
|
}
|
|
|
|
|
2021-03-03 20:30:14 +00:00
|
|
|
/**
|
|
|
|
* Tracks actions performed on a mocked dockerode instance
|
|
|
|
* @param name action called
|
|
|
|
* @param parameters data passed
|
|
|
|
*/
|
|
|
|
function addAction(name: string, parameters: Dictionary<any> = {}) {
|
2020-10-30 21:38:19 +00:00
|
|
|
actions.push({
|
|
|
|
name,
|
|
|
|
parameters,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-04-03 20:56:12 +00:00
|
|
|
type DockerodeFunction = keyof Omit<Dockerode, 'modem'>;
|
|
|
|
for (const fn of Object.getOwnPropertyNames(Dockerode.prototype)) {
|
2020-08-13 12:25:39 +00:00
|
|
|
if (
|
|
|
|
fn !== 'constructor' &&
|
2024-04-03 20:56:12 +00:00
|
|
|
typeof (Dockerode.prototype as any)[fn] === 'function'
|
2020-08-13 12:25:39 +00:00
|
|
|
) {
|
2024-04-03 20:56:12 +00:00
|
|
|
(Dockerode.prototype as any)[fn] = async function (...args: any[]) {
|
2020-08-13 12:25:39 +00:00
|
|
|
console.log(`🐳 Calling ${fn}...`);
|
|
|
|
if (overrides[fn] != null) {
|
|
|
|
return overrides[fn](args);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return promise */
|
|
|
|
return Promise.resolve([]);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// default overrides needed to startup...
|
|
|
|
registerOverride(
|
|
|
|
'getEvents',
|
|
|
|
async () =>
|
|
|
|
new Stream.Readable({
|
|
|
|
read: () => {
|
|
|
|
return _.noop();
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
2021-03-03 20:30:14 +00:00
|
|
|
/**
|
|
|
|
* Used to add or modifying functions on the mocked dockerode
|
|
|
|
* @param name function name to override
|
|
|
|
* @param fn function to execute
|
|
|
|
*/
|
2020-08-13 12:25:39 +00:00
|
|
|
export function registerOverride<
|
|
|
|
T extends DockerodeFunction,
|
2024-04-03 20:56:12 +00:00
|
|
|
P extends Parameters<Dockerode[T]>,
|
|
|
|
R extends ReturnType<Dockerode[T]>,
|
2020-08-13 12:25:39 +00:00
|
|
|
>(name: T, fn: (...args: P) => R) {
|
|
|
|
console.log(`Overriding ${name}...`);
|
|
|
|
overrides[name] = fn;
|
|
|
|
}
|
2020-07-22 09:53:50 +00:00
|
|
|
|
2021-02-12 21:16:37 +00:00
|
|
|
export function restoreOverride<T extends DockerodeFunction>(name: T) {
|
2024-03-13 07:10:08 +00:00
|
|
|
if (Object.hasOwn(overrides, name)) {
|
2021-02-12 21:16:37 +00:00
|
|
|
delete overrides[name];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-22 09:53:50 +00:00
|
|
|
export interface TestData {
|
|
|
|
networks: Dictionary<any>;
|
|
|
|
images: Dictionary<any>;
|
2020-11-19 01:37:34 +00:00
|
|
|
containers: Dictionary<any>;
|
2021-03-03 20:30:14 +00:00
|
|
|
volumes: Dictionary<any>;
|
2020-07-22 09:53:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function createMockedDockerode(data: TestData) {
|
2024-04-03 20:56:12 +00:00
|
|
|
const mockedDockerode = Dockerode.prototype;
|
2021-03-03 20:30:14 +00:00
|
|
|
|
|
|
|
mockedDockerode.listImages = async () => [];
|
|
|
|
|
|
|
|
mockedDockerode.listVolumes = async () => {
|
|
|
|
addAction('listVolumes');
|
|
|
|
return {
|
2024-04-03 20:56:12 +00:00
|
|
|
Volumes: data.volumes as Dockerode.VolumeInspectInfo[],
|
2021-03-03 20:30:14 +00:00
|
|
|
Warnings: [],
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
mockedDockerode.getVolume = (name: string) => {
|
|
|
|
addAction('getVolume');
|
|
|
|
const picked = data.volumes.filter((v: Dictionary<any>) => v.Name === name);
|
|
|
|
if (picked.length !== 1) {
|
|
|
|
throw new NotFoundError();
|
|
|
|
}
|
|
|
|
const volume = picked[0];
|
|
|
|
return {
|
|
|
|
...volume,
|
|
|
|
inspect: async () => {
|
|
|
|
addAction('inspect');
|
|
|
|
// TODO fully implement volume inspect.
|
|
|
|
// This should return VolumeInspectInfo not Volume
|
|
|
|
return volume;
|
|
|
|
},
|
2024-02-29 22:00:39 +00:00
|
|
|
remove: async (options?: any) => {
|
2021-03-03 20:30:14 +00:00
|
|
|
addAction('remove', options);
|
|
|
|
data.volumes = _.reject(data.volumes, { name: volume.name });
|
|
|
|
},
|
|
|
|
name: volume.name,
|
|
|
|
modem: {},
|
2024-04-03 20:56:12 +00:00
|
|
|
} as Dockerode.Volume;
|
2021-03-03 20:30:14 +00:00
|
|
|
};
|
|
|
|
|
2020-11-26 06:11:47 +00:00
|
|
|
mockedDockerode.createContainer = async (
|
2024-04-03 20:56:12 +00:00
|
|
|
options: Dockerode.ContainerCreateOptions,
|
2020-11-26 06:11:47 +00:00
|
|
|
) => {
|
|
|
|
addAction('createContainer', { options });
|
|
|
|
return {
|
|
|
|
start: async () => {
|
2021-03-03 20:30:14 +00:00
|
|
|
addAction('start');
|
2020-11-26 06:11:47 +00:00
|
|
|
},
|
2024-04-03 20:56:12 +00:00
|
|
|
} as Dockerode.Container;
|
2020-11-26 06:11:47 +00:00
|
|
|
};
|
2021-03-03 20:30:14 +00:00
|
|
|
|
2020-11-19 01:37:34 +00:00
|
|
|
mockedDockerode.getContainer = (id: string) => {
|
|
|
|
addAction('getContainer', { id });
|
|
|
|
return {
|
2021-03-03 20:30:14 +00:00
|
|
|
inspect: async () => {
|
|
|
|
return data.containers.filter((c: Dictionary<any>) => c.id === id);
|
|
|
|
},
|
2020-11-19 01:37:34 +00:00
|
|
|
start: async () => {
|
2021-03-03 20:30:14 +00:00
|
|
|
addAction('start');
|
2020-11-19 01:37:34 +00:00
|
|
|
data.containers = data.containers.map((c: any) => {
|
|
|
|
if (c.containerId === id) {
|
|
|
|
c.status = 'Installing';
|
|
|
|
}
|
|
|
|
return c;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
stop: async () => {
|
2021-03-03 20:30:14 +00:00
|
|
|
addAction('stop');
|
2020-11-19 01:37:34 +00:00
|
|
|
data.containers = data.containers.map((c: any) => {
|
|
|
|
if (c.containerId === id) {
|
|
|
|
c.status = 'Stopping';
|
|
|
|
}
|
|
|
|
return c;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
remove: async () => {
|
2021-03-03 20:30:14 +00:00
|
|
|
addAction('remove');
|
2020-11-19 01:37:34 +00:00
|
|
|
data.containers = data.containers.map((c: any) => {
|
|
|
|
if (c.containerId === id) {
|
|
|
|
c.status = 'removing';
|
|
|
|
}
|
|
|
|
return c;
|
|
|
|
});
|
|
|
|
},
|
2024-04-03 20:56:12 +00:00
|
|
|
} as Dockerode.Container;
|
2020-11-19 01:37:34 +00:00
|
|
|
};
|
2021-03-03 20:30:14 +00:00
|
|
|
|
2020-07-22 09:53:50 +00:00
|
|
|
mockedDockerode.getNetwork = (id: string) => {
|
2020-10-30 21:38:19 +00:00
|
|
|
addAction('getNetwork', { id });
|
2020-07-22 09:53:50 +00:00
|
|
|
return {
|
|
|
|
inspect: async () => {
|
2021-03-03 20:30:14 +00:00
|
|
|
addAction('inspect');
|
2020-07-22 09:53:50 +00:00
|
|
|
return data.networks[id];
|
|
|
|
},
|
2024-04-03 20:56:12 +00:00
|
|
|
} as Dockerode.Network;
|
2020-07-22 09:53:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
mockedDockerode.getImage = (name: string) => {
|
2020-10-30 21:38:19 +00:00
|
|
|
addAction('getImage', { name });
|
2020-07-22 09:53:50 +00:00
|
|
|
return {
|
|
|
|
inspect: async () => {
|
2021-03-03 20:30:14 +00:00
|
|
|
addAction('inspect');
|
2020-07-22 09:53:50 +00:00
|
|
|
return data.images[name];
|
|
|
|
},
|
2020-10-30 21:38:19 +00:00
|
|
|
remove: async () => {
|
2021-03-03 20:30:14 +00:00
|
|
|
addAction('remove');
|
2020-10-30 21:38:19 +00:00
|
|
|
data.images = _.reject(data.images, {
|
|
|
|
name,
|
|
|
|
});
|
|
|
|
},
|
2024-04-03 20:56:12 +00:00
|
|
|
} as Dockerode.Image;
|
2020-07-22 09:53:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return mockedDockerode;
|
|
|
|
}
|
|
|
|
|
2022-09-19 15:08:16 +00:00
|
|
|
type Prototype = { [key: string]: any };
|
2021-02-12 21:16:37 +00:00
|
|
|
function clonePrototype(prototype: Prototype): Prototype {
|
|
|
|
const clone: Prototype = {};
|
|
|
|
Object.getOwnPropertyNames(prototype).forEach((fn) => {
|
2023-10-13 14:17:17 +00:00
|
|
|
if (fn !== 'constructor' && typeof prototype[fn] === 'function') {
|
2021-02-12 21:16:37 +00:00
|
|
|
clone[fn] = prototype[fn];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return clone;
|
|
|
|
}
|
|
|
|
|
|
|
|
function assignPrototype(target: Prototype, source: Prototype) {
|
|
|
|
Object.keys(source).forEach((fn) => {
|
|
|
|
target[fn] = source[fn];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-07-22 09:53:50 +00:00
|
|
|
export async function testWithData(
|
|
|
|
data: Partial<TestData>,
|
|
|
|
test: () => Promise<any>,
|
|
|
|
) {
|
|
|
|
const mockedData: TestData = {
|
|
|
|
...{
|
2021-03-03 20:30:14 +00:00
|
|
|
networks: [],
|
|
|
|
images: [],
|
|
|
|
containers: [],
|
|
|
|
volumes: [],
|
2020-07-22 09:53:50 +00:00
|
|
|
},
|
|
|
|
...data,
|
|
|
|
};
|
|
|
|
|
|
|
|
// grab the original prototype...
|
2024-04-03 20:56:12 +00:00
|
|
|
const basePrototype = clonePrototype(Dockerode.prototype);
|
|
|
|
Dockerode.prototype = createMockedDockerode(mockedData);
|
2020-07-22 09:53:50 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
// run the test...
|
|
|
|
await test();
|
|
|
|
} finally {
|
|
|
|
// reset the original prototype...
|
2024-04-03 20:56:12 +00:00
|
|
|
assignPrototype(Dockerode.prototype, basePrototype);
|
2020-07-22 09:53:50 +00:00
|
|
|
}
|
|
|
|
}
|