mirror of
https://github.com/logos-messaging/js-waku.git
synced 2026-01-02 13:53:12 +00:00
This commit modifies functions in ServiceNode to use the REST API instead of JSON RPC when reading messages for a pubsub topic or content topic, and when ensuring a nwaku node is subscribed to a pubsub topic. Also modifies default Docker params to enable the rest API and provide a port.
227 lines
6.0 KiB
TypeScript
227 lines
6.0 KiB
TypeScript
import fs from "fs";
|
|
|
|
import { Logger } from "@waku/utils";
|
|
import Docker from "dockerode";
|
|
|
|
import { Args, Ports } from "../types.js";
|
|
|
|
const log = new Logger("test:docker");
|
|
|
|
const NETWORK_NAME = "waku";
|
|
const SUBNET = "172.18.0.0/16";
|
|
const IP_RANGE = "172.18.0.0/24";
|
|
const GATEWAY = "172.18.0.1";
|
|
|
|
export default class Dockerode {
|
|
public docker: Docker;
|
|
private readonly IMAGE_NAME: string;
|
|
public containerId?: string;
|
|
|
|
private static network: Docker.Network;
|
|
private containerIp: string;
|
|
|
|
private constructor(imageName: string, containerIp: string) {
|
|
this.docker = new Docker();
|
|
this.IMAGE_NAME = imageName;
|
|
this.containerIp = containerIp;
|
|
}
|
|
|
|
public static async createInstance(imageName: string): Promise<Dockerode> {
|
|
if (!Dockerode.network) {
|
|
Dockerode.network = await Dockerode.createNetwork(NETWORK_NAME);
|
|
}
|
|
|
|
const instance = new Dockerode(imageName, Dockerode.getNextIp());
|
|
return instance;
|
|
}
|
|
|
|
private static async createNetwork(
|
|
networkName: string = NETWORK_NAME
|
|
): Promise<Docker.Network> {
|
|
const docker = new Docker();
|
|
const networks = await docker.listNetworks();
|
|
const existingNetwork = networks.find(
|
|
(network) => network.Name === networkName
|
|
);
|
|
|
|
let network: Docker.Network;
|
|
|
|
if (existingNetwork) {
|
|
network = docker.getNetwork(existingNetwork.Id);
|
|
} else {
|
|
network = await docker.createNetwork({
|
|
Name: networkName,
|
|
Driver: "bridge",
|
|
IPAM: {
|
|
Driver: "default",
|
|
Config: [
|
|
{
|
|
Subnet: SUBNET,
|
|
IPRange: IP_RANGE,
|
|
Gateway: GATEWAY
|
|
}
|
|
]
|
|
}
|
|
});
|
|
}
|
|
|
|
return network;
|
|
}
|
|
|
|
private static getNextIp(): string {
|
|
const baseIpFragments = "172.18".split(".");
|
|
// Generate a random number between 0 and 255 for the last two fragments.
|
|
const secondLastFragment = Math.floor(Math.random() * 256); // For the .0 fragment
|
|
const lastFragment = Math.floor(Math.random() * 256); // For the last fragment
|
|
const newIp = [...baseIpFragments, secondLastFragment, lastFragment].join(
|
|
"."
|
|
);
|
|
return newIp;
|
|
}
|
|
|
|
get container(): Docker.Container | undefined {
|
|
if (!this.containerId) {
|
|
return undefined;
|
|
}
|
|
return this.docker.getContainer(this.containerId);
|
|
}
|
|
|
|
async startContainer(
|
|
ports: Ports,
|
|
args: Args,
|
|
logPath: string,
|
|
wakuServiceNodeParams?: string
|
|
): Promise<Docker.Container> {
|
|
const { rpcPort, restPort, tcpPort, websocketPort, discv5UdpPort } = ports;
|
|
|
|
await this.confirmImageExistsOrPull();
|
|
|
|
const argsArray = argsToArray(args);
|
|
if (wakuServiceNodeParams) {
|
|
argsArray.push(wakuServiceNodeParams);
|
|
}
|
|
|
|
const argsArrayWithIP = [...argsArray, `--nat=extip:${this.containerIp}`];
|
|
log.info(`Running node with args: ${argsArray.join(" ")}`);
|
|
|
|
const container = await this.docker.createContainer({
|
|
Image: this.IMAGE_NAME,
|
|
HostConfig: {
|
|
AutoRemove: true,
|
|
PortBindings: {
|
|
[`${restPort}/tcp`]: [{ HostPort: restPort.toString() }],
|
|
[`${rpcPort}/tcp`]: [{ HostPort: rpcPort.toString() }],
|
|
[`${tcpPort}/tcp`]: [{ HostPort: tcpPort.toString() }],
|
|
[`${websocketPort}/tcp`]: [{ HostPort: websocketPort.toString() }],
|
|
...(args?.peerExchange && {
|
|
[`${discv5UdpPort}/udp`]: [{ HostPort: discv5UdpPort.toString() }]
|
|
})
|
|
}
|
|
},
|
|
ExposedPorts: {
|
|
[`${restPort}/tcp`]: {},
|
|
[`${rpcPort}/tcp`]: {},
|
|
[`${tcpPort}/tcp`]: {},
|
|
[`${websocketPort}/tcp`]: {},
|
|
...(args?.peerExchange && {
|
|
[`${discv5UdpPort}/udp`]: {}
|
|
})
|
|
},
|
|
Cmd: argsArrayWithIP
|
|
});
|
|
await container.start();
|
|
|
|
await Dockerode.network.connect({
|
|
Container: container.id,
|
|
EndpointConfig: {
|
|
IPAMConfig: {
|
|
IPv4Address: this.containerIp
|
|
}
|
|
}
|
|
});
|
|
const logStream = fs.createWriteStream(logPath);
|
|
|
|
container.logs(
|
|
{ follow: true, stdout: true, stderr: true },
|
|
(err, stream) => {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
if (stream) {
|
|
stream.pipe(logStream);
|
|
}
|
|
}
|
|
);
|
|
|
|
this.containerId = container.id;
|
|
log.info(
|
|
`${this.containerId} started at ${new Date().toLocaleTimeString()}`
|
|
);
|
|
return container;
|
|
}
|
|
|
|
async stop(): Promise<void> {
|
|
if (!this.container) {
|
|
log.error("ContainerId not set");
|
|
} else {
|
|
log.info(
|
|
`Shutting down container ID ${
|
|
this.containerId
|
|
} at ${new Date().toLocaleTimeString()}`
|
|
);
|
|
|
|
await this.container.stop();
|
|
|
|
delete this.containerId;
|
|
}
|
|
}
|
|
|
|
private async confirmImageExistsOrPull(): Promise<void> {
|
|
log.info(`Confirming that image ${this.IMAGE_NAME} exists`);
|
|
|
|
const doesImageExist = this.docker.getImage(this.IMAGE_NAME);
|
|
if (!doesImageExist) {
|
|
await new Promise<void>((resolve, reject) => {
|
|
this.docker.pull(this.IMAGE_NAME, {}, (err, stream) => {
|
|
if (err) {
|
|
reject(err);
|
|
}
|
|
this.docker.modem.followProgress(stream, (err, result) => {
|
|
if (err) {
|
|
reject(err);
|
|
}
|
|
if (result) {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
log.info(`Image ${this.IMAGE_NAME} successfully found`);
|
|
}
|
|
}
|
|
|
|
export function argsToArray(args: Args): Array<string> {
|
|
const array = [];
|
|
|
|
for (const [key, value] of Object.entries(args)) {
|
|
// Change the key from camelCase to kebab-case
|
|
const kebabKey = key.replace(/([A-Z])/g, (_, capital) => {
|
|
return "-" + capital.toLowerCase();
|
|
});
|
|
|
|
if (Array.isArray(value)) {
|
|
// If the value is an array, create separate arguments for each element
|
|
value.forEach((val) => {
|
|
array.push(`--${kebabKey}=${val}`);
|
|
});
|
|
} else {
|
|
// Handle non-array values as before
|
|
const arg = `--${kebabKey}=${value}`;
|
|
array.push(arg);
|
|
}
|
|
}
|
|
|
|
return array;
|
|
}
|