Skip to content

Commit b516d56

Browse files
authored
Merge pull request #433 from dappnode/dynamic-dnp-timeout
Dynamic dnp timeout from manifest
2 parents 776534a + 2f55f74 commit b516d56

24 files changed

+224
-54
lines changed

packages/admin-ui/src/common/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ export interface ContainerLabelTypes {
327327
"dappnode.dnp.chain": ChainDriver;
328328
"dappnode.dnp.isCore": boolean;
329329
"dappnode.dnp.isMain": boolean;
330+
"dappnode.dnp.dockerTimeout": number;
330331
"dappnode.dnp.default.environment": string[];
331332
"dappnode.dnp.default.ports": string[];
332333
"dappnode.dnp.default.volumes": string[];
@@ -398,6 +399,7 @@ export interface PackageContainer {
398399
domainAlias?: string[];
399400
canBeFullnode?: boolean;
400401
isMain?: boolean;
402+
dockerTimeout?: number;
401403
// Note: environment is only accessible doing a container inspect or reading the compose
402404
// envs?: PackageEnvs;
403405
}
@@ -809,6 +811,7 @@ export type InstallPackageDataPaths = Pick<
809811
| "manifestBackupPath"
810812
| "imagePath"
811813
| "isUpdate"
814+
| "dockerTimeout"
812815
>;
813816

814817
export interface InstallPackageData extends PackageRelease {
@@ -823,6 +826,7 @@ export interface InstallPackageData extends PackageRelease {
823826
compose: Compose;
824827
// User settings to be applied after running
825828
fileUploads?: { [serviceName: string]: { [containerPath: string]: string } };
829+
dockerTimeout: number | undefined;
826830
}
827831

828832
// Must be in-sync with SDK types
@@ -847,6 +851,9 @@ export interface PackageReleaseMetadata {
847851
restartCommand?: string;
848852
restartLaunchCommand?: string;
849853

854+
// "15min" | 3600
855+
dockerTimeout?: string;
856+
850857
requirements?: {
851858
minimumDappnodeVersion: string;
852859
};

packages/dappmanager/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"socket.io": "^2.3.0",
7777
"source-map-support": "^0.5.19",
7878
"stack-trace": "^0.0.10",
79+
"timestring": "^6.0.0",
7980
"ts-loader": "^8.0.2",
8081
"tweetnacl": "^1.0.1",
8182
"tweetnacl-util": "^0.15.0",

packages/dappmanager/src/calls/packageRemove.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as getPath from "../utils/getPath";
1010
import shell from "../utils/shell";
1111
import { listPackage } from "../modules/docker/listContainers";
1212
import { logs } from "../logs";
13+
import { getDockerTimeoutMax } from "../modules/docker/utils";
1314

1415
/**
1516
* Remove package data: docker down + disk files
@@ -19,16 +20,15 @@ import { logs } from "../logs";
1920
*/
2021
export async function packageRemove({
2122
dnpName,
22-
deleteVolumes = false,
23-
timeout = 10
23+
deleteVolumes = false
2424
}: {
2525
dnpName: string;
2626
deleteVolumes?: boolean;
27-
timeout?: number;
2827
}): Promise<void> {
2928
if (!dnpName) throw Error("kwarg dnpName must be defined");
3029

3130
const dnp = await listPackage({ dnpName });
31+
const timeout = getDockerTimeoutMax(dnp.containers);
3232

3333
if (dnp.isCore || dnp.dnpName === params.dappmanagerDnpName) {
3434
throw Error("Core packages cannot be cannot be removed");
@@ -49,7 +49,8 @@ export async function packageRemove({
4949
try {
5050
await dockerComposeDown(composePath, {
5151
volumes: deleteVolumes,
52-
timeout
52+
// Ignore timeout is user doesn't want to keep any data
53+
timeout: deleteVolumes ? undefined : timeout
5354
});
5455
hasRemoved = true; // To mimic an early return
5556
} catch (e) {

packages/dappmanager/src/calls/packageRestart.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import * as eventBus from "../eventBus";
2+
import { listPackage } from "../modules/docker/listContainers";
23
import { restartPackage } from "../modules/docker/restartPackage";
4+
import { getDockerTimeoutMax } from "../modules/docker/utils";
35

46
/**
57
* Recreates a package containers
@@ -13,7 +15,14 @@ export async function packageRestart({
1315
}): Promise<void> {
1416
if (!dnpName) throw Error("kwarg dnpName must be defined");
1517

16-
await restartPackage({ dnpName, serviceNames, forceRecreate: true });
18+
const dnp = await listPackage({ dnpName });
19+
const timeout = getDockerTimeoutMax(dnp.containers);
20+
await restartPackage({
21+
dnpName,
22+
serviceNames,
23+
forceRecreate: true,
24+
timeout
25+
});
1726

1827
// Emit packages update
1928
eventBus.requestPackages.emit();

packages/dappmanager/src/calls/packageRestartVolumes.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import fs from "fs";
22
import { uniq } from "lodash";
33
import { listPackage } from "../modules/docker/listContainers";
4-
import { dockerComposeUp, dockerRm } from "../modules/docker/dockerCommands";
4+
import {
5+
dockerComposeUp,
6+
dockerRm,
7+
dockerStop
8+
} from "../modules/docker/dockerCommands";
59
import { removeNamedVolume } from "../modules/docker/removeNamedVolume";
610
import * as eventBus from "../eventBus";
711
import * as getPath from "../utils/getPath";
@@ -13,6 +17,7 @@ import {
1317
} from "../modules/docker/dockerApi";
1418
import { isVolumeOwner } from "../modules/docker/volumesData";
1519
import { InstalledPackageData } from "../types";
20+
import { getDockerTimeoutMax } from "../modules/docker/utils";
1621

1722
/**
1823
* Removes a package volumes. The re-ups the package
@@ -33,6 +38,8 @@ export async function packageRestartVolumes({
3338
// Fetching all containers to not re-fetch below
3439
const volumes = await dockerVolumesList();
3540
const dnp = await listPackage({ dnpName });
41+
const timeout = getDockerTimeoutMax(dnp.containers);
42+
3643
if (dnp.dnpName === params.dappmanagerDnpName)
3744
throw Error("The dappmanager cannot be restarted");
3845

@@ -54,14 +61,19 @@ export async function packageRestartVolumes({
5461

5562
let err: Error | null = null;
5663
try {
57-
if (containersToRemove.length > 0) await dockerRm(containersToRemove);
58-
for (const volName of volumesToRemove) await removeNamedVolume(volName);
64+
if (containersToRemove.length > 0) {
65+
await dockerStop(containersToRemove, { time: timeout });
66+
await dockerRm(containersToRemove);
67+
}
68+
for (const volName of volumesToRemove) {
69+
await removeNamedVolume(volName);
70+
}
5971
} catch (e) {
6072
err = e;
6173
}
6274

6375
// In case of error: FIRST up the dnp, THEN throw the error
64-
await dockerComposeUp(composePath);
76+
await dockerComposeUp(composePath, { timeout });
6577
if (err) throw err;
6678

6779
// Emit packages update

packages/dappmanager/src/calls/packageStartStop.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import { listPackage } from "../modules/docker/listContainers";
22
import { dockerStart, dockerStop } from "../modules/docker/dockerCommands";
33
import * as eventBus from "../eventBus";
4+
import { getDockerTimeoutMax } from "../modules/docker/utils";
45

56
/**
67
* Stops or starts a package containers
78
* @param timeout seconds to stop the package
89
*/
910
export async function packageStartStop({
1011
dnpName,
11-
serviceNames,
12-
timeout = 10
12+
serviceNames
1313
}: {
1414
dnpName: string;
1515
serviceNames?: string[];
16-
timeout?: number;
1716
}): Promise<void> {
1817
if (!dnpName) throw Error("kwarg containerName must be defined");
1918

2019
const dnp = await listPackage({ dnpName });
20+
const timeout = getDockerTimeoutMax(dnp.containers);
2121

2222
const targetContainers = dnp.containers.filter(
2323
c => !serviceNames || serviceNames.includes(c.serviceName)

packages/dappmanager/src/db/coreUpdate.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ export const coreUpdatePackagesData = {
3131
"manifestPath",
3232
"manifestBackupPath",
3333
"imagePath",
34-
"isUpdate"
34+
"isUpdate",
35+
"dockerTimeout"
3536
])
3637
)
3738
: packagesData

packages/dappmanager/src/modules/compose/labelsDb.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { pick, omitBy, mapValues } from "lodash";
1616
*/
1717

1818
const parseString = (value: string | undefined): string | undefined => value;
19+
const parseNumber = (value: string | undefined): number | undefined =>
20+
value === undefined ? undefined : parseInt(value);
1921
const parseBool = (value: string | undefined): boolean | undefined =>
2022
typeof value === "string" ? (value === "true" ? true : false) : undefined;
2123
const parseJsonSafe = <T>(value: string | undefined): T | undefined => {
@@ -26,6 +28,8 @@ const parseJsonSafe = <T>(value: string | undefined): T | undefined => {
2628
};
2729

2830
const writeString = (data: string | undefined): string | undefined => data;
31+
const writeNumber = (num: number | undefined): string | undefined =>
32+
num === undefined ? undefined : String(num);
2933
const writeBool = (data: boolean | undefined): string | undefined =>
3034
data === true ? "true" : data === false ? "false" : undefined;
3135
const writeJson = (data: object | string[]): string => JSON.stringify(data);
@@ -48,6 +52,7 @@ const labelParseFns: {
4852
: undefined,
4953
"dappnode.dnp.isCore": parseBool,
5054
"dappnode.dnp.isMain": parseBool,
55+
"dappnode.dnp.dockerTimeout": parseNumber,
5156
"dappnode.dnp.default.environment": value => parseJsonSafe(value),
5257
"dappnode.dnp.default.ports": value => parseJsonSafe(value),
5358
"dappnode.dnp.default.volumes": value => parseJsonSafe(value)
@@ -68,6 +73,7 @@ const labelStringifyFns: {
6873
"dappnode.dnp.chain": writeString,
6974
"dappnode.dnp.isCore": writeBool,
7075
"dappnode.dnp.isMain": writeBool,
76+
"dappnode.dnp.dockerTimeout": writeNumber,
7177
"dappnode.dnp.default.environment": writeJson,
7278
"dappnode.dnp.default.ports": writeJson,
7379
"dappnode.dnp.default.volumes": writeJson
@@ -128,6 +134,7 @@ export function readContainerLabels(
128134
chain: ChainDriver;
129135
isCore: boolean;
130136
isMain: boolean;
137+
dockerTimeout: number;
131138
defaultEnvironment: string[];
132139
defaultPorts: string[];
133140
defaultVolumes: string[];
@@ -144,6 +151,7 @@ export function readContainerLabels(
144151
chain: labelValues["dappnode.dnp.chain"],
145152
isCore: labelValues["dappnode.dnp.isCore"],
146153
isMain: labelValues["dappnode.dnp.isMain"],
154+
dockerTimeout: labelValues["dappnode.dnp.dockerTimeout"],
147155
defaultEnvironment: labelValues["dappnode.dnp.default.environment"],
148156
defaultPorts: labelValues["dappnode.dnp.default.ports"],
149157
defaultVolumes: labelValues["dappnode.dnp.default.volumes"]
@@ -159,7 +167,8 @@ export function writeMetadataToLabels({
159167
chain,
160168
origin,
161169
isCore,
162-
isMain
170+
isMain,
171+
dockerTimeout
163172
}: {
164173
dnpName: string;
165174
version: string;
@@ -170,6 +179,7 @@ export function writeMetadataToLabels({
170179
origin?: string;
171180
isCore?: boolean;
172181
isMain?: boolean;
182+
dockerTimeout?: number;
173183
}): ContainerLabelsRaw {
174184
return stringifyContainerLabels({
175185
"dappnode.dnp.dnpName": dnpName,
@@ -180,6 +190,7 @@ export function writeMetadataToLabels({
180190
"dappnode.dnp.origin": origin,
181191
"dappnode.dnp.chain": chain,
182192
"dappnode.dnp.isCore": isCore,
183-
"dappnode.dnp.isMain": isMain
193+
"dappnode.dnp.isMain": isMain,
194+
"dappnode.dnp.dockerTimeout": dockerTimeout
184195
});
185196
}

packages/dappmanager/src/modules/docker/dockerCommands.ts

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,36 @@ async function execDockerCompose(
2323
args: Args,
2424
kwargs?: Kwargs
2525
): Promise<string> {
26-
return shell(["docker-compose", "-f", dcPath, ...parseArgs(args, kwargs)]);
26+
return shell([
27+
"docker-compose",
28+
"-f",
29+
dcPath,
30+
...parseArgs(args, kwargs),
31+
// Adding <&- to prevent interactive mode
32+
"<&-"
33+
]);
2734
}
2835

2936
export function dockerComposeUp(
3037
dcPath: string,
31-
options?: {
38+
options: {
3239
noStart?: boolean;
40+
detach?: boolean;
3341
forceRecreate?: boolean;
42+
timeout?: number;
3443
serviceNames?: string[];
3544
removeOrphans?: boolean;
36-
}
45+
} = {}
3746
): Promise<string> {
38-
const flags: string[] = [];
39-
if (options?.noStart) flags.push("--no-start");
40-
else flags.push("--detach");
41-
if (options?.forceRecreate) flags.push("--force-recreate");
42-
if (options?.removeOrphans) flags.push("--remove-orphans");
43-
if (options?.serviceNames)
44-
for (const serviceName of options.serviceNames) flags.push(serviceName);
45-
// Adding <&- to prevent interactive mode
46-
return execDockerCompose(dcPath, ["up", ...flags, "<&-"]);
47+
// --detach is invalid with --no-start
48+
if (options.noStart) options.detach = false;
49+
return execDockerCompose(dcPath, ["up", ...(options.serviceNames || [])], {
50+
noStart: options.noStart,
51+
detach: options.detach ?? true,
52+
forceRecreate: options.forceRecreate,
53+
timeout: options.timeout,
54+
removeOrphans: options.removeOrphans
55+
});
4756
}
4857

4958
/**
@@ -52,19 +61,19 @@ export function dockerComposeUp(
5261
*/
5362
export function dockerComposeDown(
5463
dcPath: string,
55-
{ volumes, timeout }: { volumes?: boolean; timeout?: number } = {}
64+
options: { volumes?: boolean; timeout?: number } = {}
5665
): Promise<string> {
57-
return execDockerCompose(dcPath, ["down"], { volumes, timeout });
66+
return execDockerCompose(dcPath, ["down"], options);
5867
}
5968

6069
/**
6170
* Removes all containers from a compose project
62-
* -f: Don't ask to confirm removal
63-
* -s: Stop the containers, if required, before removing
71+
* --force Don't ask to confirm removal
72+
* --stop Stop the containers, if required, before removing
6473
* @param dcPath
6574
*/
6675
export function dockerComposeRm(dcPath: string): Promise<string> {
67-
return execDockerCompose(dcPath, ["rm", "-sf"]);
76+
return execDockerCompose(dcPath, ["rm"], { force: true, stop: true });
6877
}
6978

7079
export function dockerComposeStart(dcPath: string): Promise<string> {
@@ -76,9 +85,9 @@ export function dockerComposeStart(dcPath: string): Promise<string> {
7685
*/
7786
export function dockerComposeStop(
7887
dcPath: string,
79-
{ timeout }: { timeout?: number } = {}
88+
options: { timeout?: number } = {}
8089
): Promise<string> {
81-
return execDockerCompose(dcPath, ["stop"], { timeout });
90+
return execDockerCompose(dcPath, ["stop"], options);
8291
}
8392

8493
export function dockerComposeConfig(dcPath: string): Promise<string> {
@@ -97,11 +106,11 @@ export function dockerStart(
97106
}
98107

99108
export function dockerStop(
100-
containerNames: string | string[],
101-
{ time }: { time?: number } = {}
109+
containerNames: string[],
110+
options: { time?: number } = {}
102111
): Promise<string> {
103112
const ids = parseContainerIdArg(containerNames);
104-
return execDocker(["stop", ...ids], { time });
113+
return execDocker(["stop", ...ids], options);
105114
}
106115

107116
export function dockerRm(

packages/dappmanager/src/modules/docker/listContainers.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ function parseContainerInfo(container: ContainerInfo): PackageContainer {
130130
labels.defaultPorts && parsePortMappings(labels.defaultPorts);
131131
const defaultVolumes =
132132
labels.defaultVolumes && parseVolumeMappings(labels.defaultVolumes);
133+
const dockerTimeout = labels.dockerTimeout;
133134

134135
return {
135136
// Identification
@@ -190,7 +191,8 @@ function parseContainerInfo(container: ContainerInfo): PackageContainer {
190191
// Default settings on the original package version's docker-compose
191192
defaultEnvironment,
192193
defaultPorts,
193-
defaultVolumes
194+
defaultVolumes,
195+
dockerTimeout
194196
};
195197
}
196198

0 commit comments

Comments
 (0)