-
Notifications
You must be signed in to change notification settings - Fork 45
Expand file tree
/
Copy pathpackageRemove.ts
More file actions
113 lines (101 loc) · 3.7 KB
/
packageRemove.ts
File metadata and controls
113 lines (101 loc) · 3.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import fs from "fs";
import { eventBus } from "@dappnode/eventbus";
import { params } from "@dappnode/params";
import { getRepoDirPath, getDockerComposePath, getAvatarPath, shell } from "@dappnode/utils";
import { logs } from "@dappnode/logger";
import {
getDockerTimeoutMax,
dockerContainerRemove,
dockerContainerStop,
dockerComposeDown,
listPackage
} from "@dappnode/dockerapi";
import { httpsPortal } from "@dappnode/httpsportal";
import { ethicalMetricsDnpName, unregister } from "@dappnode/ethicalmetrics";
/**
* Remove package data: docker down + disk files
*
* @param id DNP .eth name
* @param deleteVolumes flag to also clear permanent package data
*/
export async function packageRemove({
dnpName,
deleteVolumes = false
}: {
dnpName: string;
deleteVolumes?: boolean;
}): Promise<void> {
if (!dnpName) throw Error("kwarg dnpName must be defined");
const dnp = await listPackage({ dnpName });
const timeout = getDockerTimeoutMax(dnp.containers);
if (
(dnp.isCore && params.corePackagesNotRemovable.includes(dnp.dnpName)) ||
dnp.dnpName === params.dappmanagerDnpName
) {
throw Error("Core packages cannot be removed");
}
// Remove portal https portal mappings if any.
// MUST removed before deleting containers
try {
httpsPortal.removeMappings(dnp);
} catch (e) {
// Bypass error to continue deleting the package
logs.error(`Error trying to remove https mappings from ${dnp.dnpName}. Continue with package remove`, e);
}
// If Ethical Metrics is being removed, unregister the instance first
if (dnp.dnpName === ethicalMetricsDnpName) {
try {
await unregister();
} catch (e) {
logs.error(`Error unregistering Ethical Metrics instance`, e);
}
}
// Only no-cores reach this block
const composePath = getDockerComposePath(dnp.dnpName, false);
const packageRepoDir = getRepoDirPath(dnp.dnpName, false);
// [NOTE] Not necessary to close the ports since they will just
// not be renewed in the next interval
// If there is no docker-compose, do a docker rm directly
// Otherwise, try to do a docker-compose down and if it fails,
// log to console and do docker-rm
let hasRemoved = false;
if (fs.existsSync(composePath)) {
try {
await dockerComposeDown(composePath, {
volumes: deleteVolumes,
// Ignore timeout is user doesn't want to keep any data
timeout: deleteVolumes ? undefined : timeout
});
hasRemoved = true; // To mimic an early return
} catch (e) {
logs.error(`Error on dockerComposeDown of ${dnp.dnpName}`, e);
}
}
if (!hasRemoved) {
const containerNames = dnp.containers.map((c) => c.containerName);
await Promise.all(
containerNames.map(async (containerName) => {
// Continue removing package even if container is already stopped
await dockerContainerStop(containerName, { timeout }).catch((e) => {
if (e.reason.includes("container already stopped") && e.statusCode === 304) return;
else throw e;
});
await dockerContainerRemove(containerName, { volumes: deleteVolumes });
})
);
}
// Remove DNP folder and files
if (fs.existsSync(packageRepoDir)) await shell(`rm -r ${packageRepoDir}`);
// Remove cached avatar:
// If we get here, the container and repo are already removed, so even if this fails
// the package is effectively removed.
try {
const avatarPath = getAvatarPath(dnp.dnpName, dnp.isCore);
if (fs.existsSync(avatarPath)) fs.unlinkSync(avatarPath);
} catch (e) {
logs.debug(`Failed to remove avatar for ${dnp.dnpName}: ${e}`);
}
// Emit packages update
eventBus.requestPackages.emit();
eventBus.packagesModified.emit({ dnpNames: [dnp.dnpName], removed: true });
}