@kapeta/local-cluster-service 0.0.0-96f91ef

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/.eslintrc.cjs +25 -0
  2. package/.github/workflows/check-license.yml +17 -0
  3. package/.github/workflows/main.yml +26 -0
  4. package/.prettierignore +4 -0
  5. package/.vscode/launch.json +19 -0
  6. package/CHANGELOG.md +920 -0
  7. package/LICENSE +38 -0
  8. package/README.md +36 -0
  9. package/definitions.d.ts +35 -0
  10. package/dist/cjs/index.d.ts +34 -0
  11. package/dist/cjs/index.js +263 -0
  12. package/dist/cjs/package.json +1 -0
  13. package/dist/cjs/src/RepositoryWatcher.d.ts +30 -0
  14. package/dist/cjs/src/RepositoryWatcher.js +332 -0
  15. package/dist/cjs/src/ai/aiClient.d.ts +20 -0
  16. package/dist/cjs/src/ai/aiClient.js +74 -0
  17. package/dist/cjs/src/ai/routes.d.ts +7 -0
  18. package/dist/cjs/src/ai/routes.js +37 -0
  19. package/dist/cjs/src/ai/transform.d.ts +11 -0
  20. package/dist/cjs/src/ai/transform.js +239 -0
  21. package/dist/cjs/src/ai/types.d.ts +40 -0
  22. package/dist/cjs/src/ai/types.js +2 -0
  23. package/dist/cjs/src/api.d.ts +7 -0
  24. package/dist/cjs/src/api.js +29 -0
  25. package/dist/cjs/src/assetManager.d.ts +41 -0
  26. package/dist/cjs/src/assetManager.js +274 -0
  27. package/dist/cjs/src/assets/routes.d.ts +7 -0
  28. package/dist/cjs/src/assets/routes.js +165 -0
  29. package/dist/cjs/src/attachments/routes.d.ts +7 -0
  30. package/dist/cjs/src/attachments/routes.js +72 -0
  31. package/dist/cjs/src/authManager.d.ts +16 -0
  32. package/dist/cjs/src/authManager.js +64 -0
  33. package/dist/cjs/src/cacheManager.d.ts +20 -0
  34. package/dist/cjs/src/cacheManager.js +51 -0
  35. package/dist/cjs/src/clusterService.d.ts +44 -0
  36. package/dist/cjs/src/clusterService.js +120 -0
  37. package/dist/cjs/src/codeGeneratorManager.d.ts +14 -0
  38. package/dist/cjs/src/codeGeneratorManager.js +93 -0
  39. package/dist/cjs/src/config/routes.d.ts +7 -0
  40. package/dist/cjs/src/config/routes.js +160 -0
  41. package/dist/cjs/src/configManager.d.ts +42 -0
  42. package/dist/cjs/src/configManager.js +136 -0
  43. package/dist/cjs/src/containerManager.d.ts +148 -0
  44. package/dist/cjs/src/containerManager.js +958 -0
  45. package/dist/cjs/src/definitionsManager.d.ts +20 -0
  46. package/dist/cjs/src/definitionsManager.js +171 -0
  47. package/dist/cjs/src/filesystem/routes.d.ts +7 -0
  48. package/dist/cjs/src/filesystem/routes.js +105 -0
  49. package/dist/cjs/src/filesystemManager.d.ts +27 -0
  50. package/dist/cjs/src/filesystemManager.js +118 -0
  51. package/dist/cjs/src/identities/routes.d.ts +7 -0
  52. package/dist/cjs/src/identities/routes.js +37 -0
  53. package/dist/cjs/src/instanceManager.d.ts +69 -0
  54. package/dist/cjs/src/instanceManager.js +910 -0
  55. package/dist/cjs/src/instances/routes.d.ts +7 -0
  56. package/dist/cjs/src/instances/routes.js +179 -0
  57. package/dist/cjs/src/middleware/cors.d.ts +6 -0
  58. package/dist/cjs/src/middleware/cors.js +14 -0
  59. package/dist/cjs/src/middleware/kapeta.d.ts +15 -0
  60. package/dist/cjs/src/middleware/kapeta.js +28 -0
  61. package/dist/cjs/src/middleware/stringBody.d.ts +9 -0
  62. package/dist/cjs/src/middleware/stringBody.js +18 -0
  63. package/dist/cjs/src/networkManager.d.ts +37 -0
  64. package/dist/cjs/src/networkManager.js +119 -0
  65. package/dist/cjs/src/operatorManager.d.ts +41 -0
  66. package/dist/cjs/src/operatorManager.js +211 -0
  67. package/dist/cjs/src/progressListener.d.ts +31 -0
  68. package/dist/cjs/src/progressListener.js +133 -0
  69. package/dist/cjs/src/providerManager.d.ts +11 -0
  70. package/dist/cjs/src/providerManager.js +84 -0
  71. package/dist/cjs/src/providers/routes.d.ts +7 -0
  72. package/dist/cjs/src/providers/routes.js +46 -0
  73. package/dist/cjs/src/proxy/routes.d.ts +7 -0
  74. package/dist/cjs/src/proxy/routes.js +115 -0
  75. package/dist/cjs/src/proxy/types/rest.d.ts +10 -0
  76. package/dist/cjs/src/proxy/types/rest.js +123 -0
  77. package/dist/cjs/src/proxy/types/web.d.ts +8 -0
  78. package/dist/cjs/src/proxy/types/web.js +61 -0
  79. package/dist/cjs/src/repositoryManager.d.ts +35 -0
  80. package/dist/cjs/src/repositoryManager.js +247 -0
  81. package/dist/cjs/src/serviceManager.d.ts +36 -0
  82. package/dist/cjs/src/serviceManager.js +106 -0
  83. package/dist/cjs/src/socketManager.d.ts +32 -0
  84. package/dist/cjs/src/socketManager.js +125 -0
  85. package/dist/cjs/src/storageService.d.ts +21 -0
  86. package/dist/cjs/src/storageService.js +81 -0
  87. package/dist/cjs/src/taskManager.d.ts +70 -0
  88. package/dist/cjs/src/taskManager.js +181 -0
  89. package/dist/cjs/src/tasks/routes.d.ts +7 -0
  90. package/dist/cjs/src/tasks/routes.js +39 -0
  91. package/dist/cjs/src/traffic/routes.d.ts +7 -0
  92. package/dist/cjs/src/traffic/routes.js +22 -0
  93. package/dist/cjs/src/types.d.ts +99 -0
  94. package/dist/cjs/src/types.js +39 -0
  95. package/dist/cjs/src/utils/BlockInstanceRunner.d.ts +28 -0
  96. package/dist/cjs/src/utils/BlockInstanceRunner.js +432 -0
  97. package/dist/cjs/src/utils/DefaultProviderInstaller.d.ts +15 -0
  98. package/dist/cjs/src/utils/DefaultProviderInstaller.js +136 -0
  99. package/dist/cjs/src/utils/InternalConfigProvider.d.ts +38 -0
  100. package/dist/cjs/src/utils/InternalConfigProvider.js +146 -0
  101. package/dist/cjs/src/utils/LogData.d.ts +23 -0
  102. package/dist/cjs/src/utils/LogData.js +46 -0
  103. package/dist/cjs/src/utils/commandLineUtils.d.ts +8 -0
  104. package/dist/cjs/src/utils/commandLineUtils.js +39 -0
  105. package/dist/cjs/src/utils/pathTemplateParser.d.ts +30 -0
  106. package/dist/cjs/src/utils/pathTemplateParser.js +135 -0
  107. package/dist/cjs/src/utils/utils.d.ts +40 -0
  108. package/dist/cjs/src/utils/utils.js +148 -0
  109. package/dist/cjs/start.d.ts +5 -0
  110. package/dist/cjs/start.js +17 -0
  111. package/dist/cjs/test/proxy/types/rest.test.d.ts +5 -0
  112. package/dist/cjs/test/proxy/types/rest.test.js +48 -0
  113. package/dist/cjs/test/utils/pathTemplateParser.test.d.ts +5 -0
  114. package/dist/cjs/test/utils/pathTemplateParser.test.js +27 -0
  115. package/dist/esm/index.d.ts +34 -0
  116. package/dist/esm/index.js +263 -0
  117. package/dist/esm/package.json +1 -0
  118. package/dist/esm/src/RepositoryWatcher.d.ts +30 -0
  119. package/dist/esm/src/RepositoryWatcher.js +332 -0
  120. package/dist/esm/src/ai/aiClient.d.ts +20 -0
  121. package/dist/esm/src/ai/aiClient.js +74 -0
  122. package/dist/esm/src/ai/routes.d.ts +7 -0
  123. package/dist/esm/src/ai/routes.js +37 -0
  124. package/dist/esm/src/ai/transform.d.ts +11 -0
  125. package/dist/esm/src/ai/transform.js +239 -0
  126. package/dist/esm/src/ai/types.d.ts +40 -0
  127. package/dist/esm/src/ai/types.js +2 -0
  128. package/dist/esm/src/api.d.ts +7 -0
  129. package/dist/esm/src/api.js +29 -0
  130. package/dist/esm/src/assetManager.d.ts +41 -0
  131. package/dist/esm/src/assetManager.js +274 -0
  132. package/dist/esm/src/assets/routes.d.ts +7 -0
  133. package/dist/esm/src/assets/routes.js +165 -0
  134. package/dist/esm/src/attachments/routes.d.ts +7 -0
  135. package/dist/esm/src/attachments/routes.js +72 -0
  136. package/dist/esm/src/authManager.d.ts +16 -0
  137. package/dist/esm/src/authManager.js +64 -0
  138. package/dist/esm/src/cacheManager.d.ts +20 -0
  139. package/dist/esm/src/cacheManager.js +51 -0
  140. package/dist/esm/src/clusterService.d.ts +44 -0
  141. package/dist/esm/src/clusterService.js +120 -0
  142. package/dist/esm/src/codeGeneratorManager.d.ts +14 -0
  143. package/dist/esm/src/codeGeneratorManager.js +93 -0
  144. package/dist/esm/src/config/routes.d.ts +7 -0
  145. package/dist/esm/src/config/routes.js +160 -0
  146. package/dist/esm/src/configManager.d.ts +42 -0
  147. package/dist/esm/src/configManager.js +136 -0
  148. package/dist/esm/src/containerManager.d.ts +148 -0
  149. package/dist/esm/src/containerManager.js +958 -0
  150. package/dist/esm/src/definitionsManager.d.ts +20 -0
  151. package/dist/esm/src/definitionsManager.js +171 -0
  152. package/dist/esm/src/filesystem/routes.d.ts +7 -0
  153. package/dist/esm/src/filesystem/routes.js +105 -0
  154. package/dist/esm/src/filesystemManager.d.ts +27 -0
  155. package/dist/esm/src/filesystemManager.js +118 -0
  156. package/dist/esm/src/identities/routes.d.ts +7 -0
  157. package/dist/esm/src/identities/routes.js +37 -0
  158. package/dist/esm/src/instanceManager.d.ts +69 -0
  159. package/dist/esm/src/instanceManager.js +910 -0
  160. package/dist/esm/src/instances/routes.d.ts +7 -0
  161. package/dist/esm/src/instances/routes.js +179 -0
  162. package/dist/esm/src/middleware/cors.d.ts +6 -0
  163. package/dist/esm/src/middleware/cors.js +14 -0
  164. package/dist/esm/src/middleware/kapeta.d.ts +15 -0
  165. package/dist/esm/src/middleware/kapeta.js +28 -0
  166. package/dist/esm/src/middleware/stringBody.d.ts +9 -0
  167. package/dist/esm/src/middleware/stringBody.js +18 -0
  168. package/dist/esm/src/networkManager.d.ts +37 -0
  169. package/dist/esm/src/networkManager.js +119 -0
  170. package/dist/esm/src/operatorManager.d.ts +41 -0
  171. package/dist/esm/src/operatorManager.js +211 -0
  172. package/dist/esm/src/progressListener.d.ts +31 -0
  173. package/dist/esm/src/progressListener.js +133 -0
  174. package/dist/esm/src/providerManager.d.ts +11 -0
  175. package/dist/esm/src/providerManager.js +84 -0
  176. package/dist/esm/src/providers/routes.d.ts +7 -0
  177. package/dist/esm/src/providers/routes.js +46 -0
  178. package/dist/esm/src/proxy/routes.d.ts +7 -0
  179. package/dist/esm/src/proxy/routes.js +115 -0
  180. package/dist/esm/src/proxy/types/rest.d.ts +10 -0
  181. package/dist/esm/src/proxy/types/rest.js +123 -0
  182. package/dist/esm/src/proxy/types/web.d.ts +8 -0
  183. package/dist/esm/src/proxy/types/web.js +61 -0
  184. package/dist/esm/src/repositoryManager.d.ts +35 -0
  185. package/dist/esm/src/repositoryManager.js +247 -0
  186. package/dist/esm/src/serviceManager.d.ts +36 -0
  187. package/dist/esm/src/serviceManager.js +106 -0
  188. package/dist/esm/src/socketManager.d.ts +32 -0
  189. package/dist/esm/src/socketManager.js +125 -0
  190. package/dist/esm/src/storageService.d.ts +21 -0
  191. package/dist/esm/src/storageService.js +81 -0
  192. package/dist/esm/src/taskManager.d.ts +70 -0
  193. package/dist/esm/src/taskManager.js +181 -0
  194. package/dist/esm/src/tasks/routes.d.ts +7 -0
  195. package/dist/esm/src/tasks/routes.js +39 -0
  196. package/dist/esm/src/traffic/routes.d.ts +7 -0
  197. package/dist/esm/src/traffic/routes.js +22 -0
  198. package/dist/esm/src/types.d.ts +99 -0
  199. package/dist/esm/src/types.js +39 -0
  200. package/dist/esm/src/utils/BlockInstanceRunner.d.ts +28 -0
  201. package/dist/esm/src/utils/BlockInstanceRunner.js +432 -0
  202. package/dist/esm/src/utils/DefaultProviderInstaller.d.ts +15 -0
  203. package/dist/esm/src/utils/DefaultProviderInstaller.js +136 -0
  204. package/dist/esm/src/utils/InternalConfigProvider.d.ts +38 -0
  205. package/dist/esm/src/utils/InternalConfigProvider.js +146 -0
  206. package/dist/esm/src/utils/LogData.d.ts +23 -0
  207. package/dist/esm/src/utils/LogData.js +46 -0
  208. package/dist/esm/src/utils/commandLineUtils.d.ts +8 -0
  209. package/dist/esm/src/utils/commandLineUtils.js +39 -0
  210. package/dist/esm/src/utils/pathTemplateParser.d.ts +30 -0
  211. package/dist/esm/src/utils/pathTemplateParser.js +135 -0
  212. package/dist/esm/src/utils/utils.d.ts +40 -0
  213. package/dist/esm/src/utils/utils.js +148 -0
  214. package/dist/esm/start.d.ts +5 -0
  215. package/dist/esm/start.js +17 -0
  216. package/dist/esm/test/proxy/types/rest.test.d.ts +5 -0
  217. package/dist/esm/test/proxy/types/rest.test.js +48 -0
  218. package/dist/esm/test/utils/pathTemplateParser.test.d.ts +5 -0
  219. package/dist/esm/test/utils/pathTemplateParser.test.js +27 -0
  220. package/index.ts +280 -0
  221. package/jest.config.js +8 -0
  222. package/package.json +134 -0
  223. package/src/RepositoryWatcher.ts +363 -0
  224. package/src/ai/aiClient.ts +93 -0
  225. package/src/ai/routes.ts +39 -0
  226. package/src/ai/transform.ts +275 -0
  227. package/src/ai/types.ts +45 -0
  228. package/src/api.ts +32 -0
  229. package/src/assetManager.ts +355 -0
  230. package/src/assets/routes.ts +183 -0
  231. package/src/attachments/routes.ts +79 -0
  232. package/src/authManager.ts +67 -0
  233. package/src/cacheManager.ts +59 -0
  234. package/src/clusterService.ts +142 -0
  235. package/src/codeGeneratorManager.ts +109 -0
  236. package/src/config/routes.ts +201 -0
  237. package/src/configManager.ts +180 -0
  238. package/src/containerManager.ts +1178 -0
  239. package/src/definitionsManager.ts +212 -0
  240. package/src/filesystem/routes.ts +123 -0
  241. package/src/filesystemManager.ts +133 -0
  242. package/src/identities/routes.ts +38 -0
  243. package/src/instanceManager.ts +1160 -0
  244. package/src/instances/routes.ts +203 -0
  245. package/src/middleware/cors.ts +14 -0
  246. package/src/middleware/kapeta.ts +41 -0
  247. package/src/middleware/stringBody.ts +21 -0
  248. package/src/networkManager.ts +148 -0
  249. package/src/operatorManager.ts +294 -0
  250. package/src/progressListener.ts +151 -0
  251. package/src/providerManager.ts +97 -0
  252. package/src/providers/routes.ts +51 -0
  253. package/src/proxy/routes.ts +153 -0
  254. package/src/proxy/types/rest.ts +172 -0
  255. package/src/proxy/types/web.ts +70 -0
  256. package/src/repositoryManager.ts +291 -0
  257. package/src/serviceManager.ts +133 -0
  258. package/src/socketManager.ts +138 -0
  259. package/src/storageService.ts +97 -0
  260. package/src/taskManager.ts +247 -0
  261. package/src/tasks/routes.ts +43 -0
  262. package/src/traffic/routes.ts +23 -0
  263. package/src/types.ts +112 -0
  264. package/src/utils/BlockInstanceRunner.ts +577 -0
  265. package/src/utils/DefaultProviderInstaller.ts +150 -0
  266. package/src/utils/InternalConfigProvider.ts +214 -0
  267. package/src/utils/LogData.ts +50 -0
  268. package/src/utils/commandLineUtils.ts +45 -0
  269. package/src/utils/pathTemplateParser.ts +157 -0
  270. package/src/utils/utils.ts +155 -0
  271. package/start.ts +14 -0
  272. package/test/proxy/types/rest.test.ts +54 -0
  273. package/test/utils/pathTemplateParser.test.ts +29 -0
  274. package/tsconfig.json +15 -0
@@ -0,0 +1,958 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright 2023 Kapeta Inc.
4
+ * SPDX-License-Identifier: BUSL-1.1
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.containerManager = exports.toLocalBindVolume = exports.getExtraHosts = exports.ContainerInfo = exports.HEALTH_CHECK_TIMEOUT = exports.COMPOSE_LABEL_SERVICE = exports.COMPOSE_LABEL_PROJECT = exports.CONTAINER_LABEL_PORT_PREFIX = void 0;
11
+ const path_1 = __importDefault(require("path"));
12
+ const storageService_1 = require("./storageService");
13
+ const os_1 = __importDefault(require("os"));
14
+ const lodash_1 = __importDefault(require("lodash"));
15
+ const fs_extra_1 = __importDefault(require("fs-extra"));
16
+ const dockerode_1 = __importDefault(require("dockerode"));
17
+ const nodejs_utils_1 = require("@kapeta/nodejs-utils");
18
+ const local_cluster_config_1 = __importDefault(require("@kapeta/local-cluster-config"));
19
+ const node_uuid_1 = __importDefault(require("node-uuid"));
20
+ const md5_1 = __importDefault(require("md5"));
21
+ const utils_1 = require("./utils/utils");
22
+ const types_1 = require("./types");
23
+ const nodejs_api_client_1 = require("@kapeta/nodejs-api-client");
24
+ const taskManager_1 = require("./taskManager");
25
+ const node_events_1 = require("node:events");
26
+ const StreamValues_1 = __importDefault(require("stream-json/streamers/StreamValues"));
27
+ exports.CONTAINER_LABEL_PORT_PREFIX = 'kapeta_port-';
28
+ const NANO_SECOND = 1000000;
29
+ const HEALTH_CHECK_INTERVAL = 3000;
30
+ const HEALTH_CHECK_MAX = 100;
31
+ const LATEST_PULL_TIMEOUT = 1000 * 60 * 15; // 15 minutes
32
+ exports.COMPOSE_LABEL_PROJECT = 'com.docker.compose.project';
33
+ exports.COMPOSE_LABEL_SERVICE = 'com.docker.compose.service';
34
+ exports.HEALTH_CHECK_TIMEOUT = HEALTH_CHECK_INTERVAL * HEALTH_CHECK_MAX * 2;
35
+ var DockerPullEventTypes;
36
+ (function (DockerPullEventTypes) {
37
+ DockerPullEventTypes["PreparingPhase"] = "Preparing";
38
+ DockerPullEventTypes["WaitingPhase"] = "Waiting";
39
+ DockerPullEventTypes["PullingFsPhase"] = "Pulling fs layer";
40
+ DockerPullEventTypes["DownloadingPhase"] = "Downloading";
41
+ DockerPullEventTypes["DownloadCompletePhase"] = "Download complete";
42
+ DockerPullEventTypes["ExtractingPhase"] = "Extracting";
43
+ DockerPullEventTypes["VerifyingChecksumPhase"] = "Verifying Checksum";
44
+ DockerPullEventTypes["AlreadyExistsPhase"] = "Already exists";
45
+ DockerPullEventTypes["PullCompletePhase"] = "Pull complete";
46
+ })(DockerPullEventTypes || (DockerPullEventTypes = {}));
47
+ const processJsonStream = (purpose, stream, handler) => new Promise((resolve, reject) => {
48
+ const jsonStream = StreamValues_1.default.withParser();
49
+ jsonStream.on('data', (data) => {
50
+ try {
51
+ handler(data.value);
52
+ }
53
+ catch (e) {
54
+ console.error('Failed while processing data for stream: %s', purpose, e);
55
+ }
56
+ });
57
+ jsonStream.on('end', () => {
58
+ console.log('Docker stream ended: %s', purpose);
59
+ resolve();
60
+ });
61
+ jsonStream.on('error', (err) => {
62
+ console.error('Docker stream failed: %s', purpose, err);
63
+ reject(err);
64
+ });
65
+ stream.pipe(jsonStream);
66
+ });
67
+ class ContainerManager {
68
+ _docker;
69
+ _alive;
70
+ _mountDir;
71
+ _version;
72
+ _lastDockerAccessCheck = 0;
73
+ logStreams = {};
74
+ _latestImagePulls = {};
75
+ constructor() {
76
+ this._docker = null;
77
+ this._alive = false;
78
+ this._version = '';
79
+ this._mountDir = path_1.default.join(storageService_1.storageService.getKapetaBasedir(), 'mounts');
80
+ this._latestImagePulls = {};
81
+ fs_extra_1.default.mkdirpSync(this._mountDir);
82
+ }
83
+ async initialize() {
84
+ // Use the value from cluster-service.yml if configured
85
+ const dockerConfig = local_cluster_config_1.default.getDockerConfig();
86
+ const connectOptions = Object.keys(dockerConfig).length > 0
87
+ ? [dockerConfig]
88
+ : [
89
+ // use defaults: DOCKER_HOST etc from env, if available
90
+ undefined,
91
+ // default linux
92
+ { socketPath: '/var/run/docker.sock' },
93
+ // default macOS
94
+ {
95
+ socketPath: path_1.default.join(os_1.default.homedir(), '.docker/run/docker.sock'),
96
+ },
97
+ // Default http
98
+ { protocol: 'http', host: 'localhost', port: 2375 },
99
+ { protocol: 'https', host: 'localhost', port: 2376 },
100
+ { protocol: 'http', host: '127.0.0.1', port: 2375 },
101
+ { protocol: 'https', host: '127.0.0.1', port: 2376 },
102
+ ];
103
+ for (const opts of connectOptions) {
104
+ try {
105
+ const testClient = new dockerode_1.default({
106
+ ...opts,
107
+ timeout: 1000, // 1 secs should be enough for a ping
108
+ });
109
+ await testClient.ping();
110
+ // If we get here - we have a working connection
111
+ // Now create a client with a longer timeout for all other operations
112
+ const client = new dockerode_1.default({
113
+ ...opts,
114
+ timeout: 15 * 60 * 1000, //15 minutes should be enough for any operation
115
+ });
116
+ this._docker = client;
117
+ const versionInfo = await client.version();
118
+ this._version = versionInfo.Server?.Version ?? versionInfo.Version;
119
+ if (!this._version) {
120
+ console.warn('Failed to determine version from response', versionInfo);
121
+ this._version = '0.0.0';
122
+ }
123
+ this._alive = true;
124
+ console.log('Connected to docker daemon with version: %s', this._version);
125
+ return;
126
+ }
127
+ catch (err) {
128
+ // silently ignore bad configs
129
+ }
130
+ }
131
+ throw new Error('Could not connect to docker daemon. Please make sure docker is running and working.');
132
+ }
133
+ async checkAlive() {
134
+ if (!this._docker) {
135
+ try {
136
+ await this.initialize();
137
+ }
138
+ catch (e) {
139
+ this._alive = false;
140
+ }
141
+ return this._alive;
142
+ }
143
+ try {
144
+ await this._docker.ping();
145
+ this._alive = true;
146
+ }
147
+ catch (e) {
148
+ this._alive = false;
149
+ }
150
+ return this._alive;
151
+ }
152
+ isAlive() {
153
+ return this._alive;
154
+ }
155
+ getMountPoint(systemId, ref, mountName) {
156
+ const kindUri = (0, nodejs_utils_1.parseKapetaUri)(ref);
157
+ const systemUri = (0, nodejs_utils_1.parseKapetaUri)(systemId);
158
+ return path_1.default.join(this._mountDir, systemUri.handle, systemUri.name, systemUri.version, kindUri.handle, kindUri.name, kindUri.version, mountName);
159
+ }
160
+ async createMounts(systemId, kind, mountOpts) {
161
+ const mounts = {};
162
+ if (mountOpts) {
163
+ const mountOptList = Object.entries(mountOpts);
164
+ for (const [mountName, containerPath] of mountOptList) {
165
+ const hostPath = this.getMountPoint(systemId, kind, mountName);
166
+ await fs_extra_1.default.mkdirp(hostPath);
167
+ mounts[containerPath] = hostPath;
168
+ }
169
+ }
170
+ return mounts;
171
+ }
172
+ async createVolumes(systemId, serviceId, mountOpts) {
173
+ const Mounts = [];
174
+ if (mountOpts) {
175
+ const mountOptList = Object.entries(mountOpts);
176
+ for (const [mountName, containerPath] of mountOptList) {
177
+ const volumeName = `${systemId}_${serviceId}_${mountName}`.replace(/[^a-z0-9]/gi, '_');
178
+ Mounts.push({
179
+ Target: containerPath,
180
+ Source: volumeName,
181
+ Type: 'volume',
182
+ ReadOnly: false,
183
+ Consistency: 'consistent',
184
+ Labels: {
185
+ [exports.COMPOSE_LABEL_PROJECT]: systemId.replace(/[^a-z0-9]/gi, '_'),
186
+ [exports.COMPOSE_LABEL_SERVICE]: serviceId.replace(/[^a-z0-9]/gi, '_'),
187
+ },
188
+ });
189
+ }
190
+ }
191
+ return Mounts;
192
+ }
193
+ async ping() {
194
+ try {
195
+ const pingResult = await this.docker().ping();
196
+ if (pingResult !== 'OK') {
197
+ throw new Error(`Ping failed: ${pingResult}`);
198
+ }
199
+ }
200
+ catch (e) {
201
+ throw new Error(`Docker not running. Please start the docker daemon before running this command. Error: ${e.message}`);
202
+ }
203
+ }
204
+ docker() {
205
+ if (!this._docker) {
206
+ throw new Error(`Docker not running`);
207
+ }
208
+ return this._docker;
209
+ }
210
+ async getContainerByName(containerName) {
211
+ // The container can be fetched by name or by id using the same API call
212
+ return this.get(containerName);
213
+ }
214
+ async pull(image) {
215
+ let [imageName, tag] = image.split(/:/);
216
+ if (!tag) {
217
+ tag = 'latest';
218
+ }
219
+ const imageTagList = (await this.docker().listImages({}))
220
+ .filter((imageData) => !!imageData.RepoTags)
221
+ .map((imageData) => imageData.RepoTags);
222
+ const imageExists = imageTagList.some((imageTags) => imageTags.includes(image));
223
+ if (tag === 'latest') {
224
+ if (imageExists && this._latestImagePulls[imageName]) {
225
+ const lastPull = this._latestImagePulls[imageName];
226
+ const timeSinceLastPull = Date.now() - lastPull;
227
+ if (timeSinceLastPull < LATEST_PULL_TIMEOUT) {
228
+ console.log('Image found and was pulled %s seconds ago: %s', Math.round(timeSinceLastPull / 1000), image);
229
+ // Last pull was less than the timeout - don't pull again
230
+ return false;
231
+ }
232
+ }
233
+ this._latestImagePulls[imageName] = Date.now();
234
+ }
235
+ else if (imageExists) {
236
+ console.log('Image found: %s', image);
237
+ return false;
238
+ }
239
+ let friendlyImageName = image;
240
+ const imageParts = imageName.split('/');
241
+ if (imageParts.length > 2) {
242
+ //Strip the registry to make the name shorter
243
+ friendlyImageName = `${imageParts.slice(1).join('/')}:${tag}`;
244
+ }
245
+ const taskName = `Pulling image ${friendlyImageName}`;
246
+ const processor = async (task) => {
247
+ const timeStarted = Date.now();
248
+ const api = new nodejs_api_client_1.KapetaAPI();
249
+ const accessToken = api.hasToken() ? await api.getAccessToken() : null;
250
+ const auth = accessToken && image.startsWith('docker.kapeta.com/')
251
+ ? {
252
+ username: 'kapeta',
253
+ password: accessToken,
254
+ serveraddress: 'docker.kapeta.com',
255
+ }
256
+ : {};
257
+ const stream = await this.docker().pull(image, {
258
+ authconfig: auth,
259
+ });
260
+ const chunks = {};
261
+ let lastEmitted = Date.now();
262
+ await processJsonStream(`image:pull:${image}`, stream, (data) => {
263
+ if (!chunks[data.id]) {
264
+ chunks[data.id] = {
265
+ downloading: {
266
+ total: 0,
267
+ current: 0,
268
+ },
269
+ extracting: {
270
+ total: 0,
271
+ current: 0,
272
+ },
273
+ done: false,
274
+ };
275
+ }
276
+ const chunk = chunks[data.id];
277
+ if (data.stream) {
278
+ // Emit raw output to the task log
279
+ task.addLog(data.stream);
280
+ }
281
+ switch (data.status) {
282
+ case DockerPullEventTypes.PreparingPhase:
283
+ case DockerPullEventTypes.WaitingPhase:
284
+ case DockerPullEventTypes.PullingFsPhase:
285
+ //Do nothing
286
+ break;
287
+ case DockerPullEventTypes.DownloadingPhase:
288
+ case DockerPullEventTypes.VerifyingChecksumPhase:
289
+ chunk.downloading = {
290
+ total: data.progressDetail?.total ?? 0,
291
+ current: data.progressDetail?.current ?? 0,
292
+ };
293
+ break;
294
+ case DockerPullEventTypes.ExtractingPhase:
295
+ chunk.extracting = {
296
+ total: data.progressDetail?.total ?? 0,
297
+ current: data.progressDetail?.current ?? 0,
298
+ };
299
+ break;
300
+ case DockerPullEventTypes.DownloadCompletePhase:
301
+ chunk.downloading.current = chunks[data.id].downloading.total;
302
+ break;
303
+ case DockerPullEventTypes.PullCompletePhase:
304
+ chunk.extracting.current = chunks[data.id].extracting.total;
305
+ chunk.done = true;
306
+ break;
307
+ }
308
+ if (data.status === DockerPullEventTypes.AlreadyExistsPhase ||
309
+ data.status.includes('Image is up to date') ||
310
+ data.status.includes('Downloaded newer image')) {
311
+ chunk.downloading.current = 1;
312
+ chunk.downloading.total = 1;
313
+ chunk.extracting.current = 1;
314
+ chunk.extracting.total = 1;
315
+ chunk.done = true;
316
+ }
317
+ const chunkList = Object.values(chunks);
318
+ let totals = {
319
+ downloading: {
320
+ total: 0,
321
+ current: 0,
322
+ },
323
+ extracting: {
324
+ total: 0,
325
+ current: 0,
326
+ },
327
+ percent: 0,
328
+ total: chunkList.length,
329
+ done: 0,
330
+ };
331
+ chunkList.forEach((chunk) => {
332
+ if (chunk.downloading.current > 0) {
333
+ totals.downloading.current += chunk.downloading.current;
334
+ }
335
+ if (chunk.downloading.total > 0) {
336
+ totals.downloading.total += chunk.downloading.total;
337
+ }
338
+ if (chunk.extracting.current > 0) {
339
+ totals.extracting.current += chunk.extracting.current;
340
+ }
341
+ if (chunk.extracting.total > 0) {
342
+ totals.extracting.total += chunk.extracting.total;
343
+ }
344
+ if (chunk.done) {
345
+ totals.done++;
346
+ }
347
+ });
348
+ totals.percent = totals.total > 0 ? (totals.done / totals.total) * 100 : 0;
349
+ task.metadata = {
350
+ ...task.metadata,
351
+ image,
352
+ progress: totals.percent,
353
+ status: totals,
354
+ timeTaken: Date.now() - timeStarted,
355
+ };
356
+ if (Date.now() - lastEmitted < 1000) {
357
+ return;
358
+ }
359
+ task.emitUpdate();
360
+ lastEmitted = Date.now();
361
+ //console.log('Pulling image %s: %s % [done: %s, total: %s]', image, Math.round(percent), totals.done, totals.total);
362
+ });
363
+ task.metadata = {
364
+ ...task.metadata,
365
+ image,
366
+ progress: 100,
367
+ timeTaken: Date.now() - timeStarted,
368
+ };
369
+ task.emitUpdate();
370
+ };
371
+ const task = taskManager_1.taskManager.add(`docker:image:pull:${image}`, processor, {
372
+ name: taskName,
373
+ image,
374
+ progress: -1,
375
+ group: 'docker:pull', //It's faster to pull images one at a time
376
+ });
377
+ await task.wait();
378
+ return true;
379
+ }
380
+ toDockerMounts(mounts) {
381
+ const Mounts = [];
382
+ lodash_1.default.forEach(mounts, (Source, Target) => {
383
+ Mounts.push({
384
+ Target,
385
+ Source: toLocalBindVolume(Source),
386
+ Type: 'bind',
387
+ ReadOnly: false,
388
+ Consistency: 'consistent',
389
+ });
390
+ });
391
+ return Mounts;
392
+ }
393
+ toDockerHealth(health) {
394
+ return {
395
+ Test: ['CMD-SHELL', health.cmd],
396
+ Interval: health.interval ? health.interval * NANO_SECOND : 5000 * NANO_SECOND,
397
+ Timeout: health.timeout ? health.timeout * NANO_SECOND : 15000 * NANO_SECOND,
398
+ Retries: health.retries || 10,
399
+ };
400
+ }
401
+ applyHash(dockerOpts) {
402
+ if (dockerOpts?.Labels?.HASH) {
403
+ delete dockerOpts.Labels.HASH;
404
+ }
405
+ const hash = (0, md5_1.default)(JSON.stringify(dockerOpts));
406
+ if (!dockerOpts.Labels) {
407
+ dockerOpts.Labels = {};
408
+ }
409
+ dockerOpts.Labels.HASH = hash;
410
+ }
411
+ async ensureContainer(opts) {
412
+ return await this.createOrUpdateContainer(opts);
413
+ }
414
+ async createOrUpdateContainer(opts) {
415
+ let imagePulled = await this.pull(opts.Image);
416
+ this.applyHash(opts);
417
+ if (!opts.name) {
418
+ console.log('Starting unnamed container: %s', opts.Image);
419
+ return this.startContainer(opts);
420
+ }
421
+ const container = await this.getContainerByName(opts.name);
422
+ if (imagePulled) {
423
+ // If image was pulled always recreate
424
+ console.log('New version of image was pulled: %s', opts.Image);
425
+ }
426
+ else {
427
+ if (!container) {
428
+ console.log('Starting new container: %s', opts.name);
429
+ return this.startContainer(opts);
430
+ }
431
+ const containerData = await container.inspect();
432
+ if (containerData?.Config.Labels?.HASH === opts.Labels.HASH) {
433
+ if (!(await container.isRunning())) {
434
+ console.log('Starting previously created container: %s', opts.name);
435
+ await container.start();
436
+ }
437
+ else {
438
+ console.log('Previously created container already running: %s', opts.name);
439
+ }
440
+ return container.native;
441
+ }
442
+ }
443
+ if (container) {
444
+ // Remove the container and start a new one
445
+ console.log('Replacing previously created container: %s', opts.name);
446
+ await container.remove({ force: true });
447
+ }
448
+ console.log('Starting new container: %s', opts.name);
449
+ return this.startContainer(opts);
450
+ }
451
+ async startContainer(opts) {
452
+ const extraHosts = getExtraHosts(this._version);
453
+ if (extraHosts && extraHosts.length > 0) {
454
+ if (!opts.HostConfig) {
455
+ opts.HostConfig = {};
456
+ }
457
+ if (!opts.HostConfig.ExtraHosts) {
458
+ opts.HostConfig.ExtraHosts = [];
459
+ }
460
+ opts.HostConfig.ExtraHosts = opts.HostConfig.ExtraHosts.concat(extraHosts);
461
+ }
462
+ const dockerContainer = await this.docker().createContainer(opts);
463
+ await dockerContainer.start();
464
+ return dockerContainer;
465
+ }
466
+ async waitForReady(container, attempt = 0) {
467
+ if (!attempt) {
468
+ attempt = 0;
469
+ }
470
+ if (attempt >= HEALTH_CHECK_MAX) {
471
+ throw new Error('Container did not become ready within the timeout');
472
+ }
473
+ if (await this._isReady(container)) {
474
+ return;
475
+ }
476
+ return new Promise((resolve, reject) => {
477
+ setTimeout(async () => {
478
+ try {
479
+ await this.waitForReady(container, attempt + 1);
480
+ resolve();
481
+ }
482
+ catch (err) {
483
+ reject(err);
484
+ }
485
+ }, HEALTH_CHECK_INTERVAL);
486
+ });
487
+ }
488
+ async _isReady(container) {
489
+ let info;
490
+ try {
491
+ info = await container.inspect();
492
+ }
493
+ catch (err) {
494
+ return false;
495
+ }
496
+ const state = info.State;
497
+ if (state.Status === 'exited' || state?.Status === 'removing' || state?.Status === 'dead') {
498
+ throw new Error('Container exited unexpectedly');
499
+ }
500
+ if (state.Health) {
501
+ // If container has health info - wait for it to become healthy
502
+ return state.Health.Status === 'healthy';
503
+ }
504
+ else {
505
+ return state.Running ?? false;
506
+ }
507
+ }
508
+ async remove(container, opts) {
509
+ const newName = 'deleting-' + node_uuid_1.default.v4();
510
+ // Rename the container first to avoid name conflicts if people start the same container
511
+ await container.rename({ name: newName });
512
+ const newContainer = this.docker().getContainer(newName);
513
+ await newContainer.remove({ force: !!opts?.force });
514
+ }
515
+ /**
516
+ *
517
+ * @param name
518
+ * @return {Promise<ContainerInfo>}
519
+ */
520
+ async get(name) {
521
+ let dockerContainer = null;
522
+ try {
523
+ dockerContainer = this.docker().getContainer(name);
524
+ await dockerContainer.stats();
525
+ }
526
+ catch (err) {
527
+ //Ignore
528
+ dockerContainer = null;
529
+ }
530
+ if (!dockerContainer) {
531
+ return undefined;
532
+ }
533
+ return new ContainerInfo(dockerContainer);
534
+ }
535
+ async getLogs(instance) {
536
+ const containerName = await (0, utils_1.getBlockInstanceContainerName)(instance.systemId, instance.instanceId);
537
+ const containerInfo = await this.getContainerByName(containerName);
538
+ if (!containerInfo) {
539
+ return [
540
+ {
541
+ source: 'stdout',
542
+ level: 'ERROR',
543
+ time: Date.now(),
544
+ message: 'Container not found',
545
+ },
546
+ ];
547
+ }
548
+ return await containerInfo.getLogs();
549
+ }
550
+ async stopLogListening(systemId, instanceId) {
551
+ const containerName = await (0, utils_1.getBlockInstanceContainerName)(systemId, instanceId);
552
+ if (this.logStreams[containerName]) {
553
+ if (this.logStreams[containerName]?.timer) {
554
+ clearTimeout(this.logStreams[containerName].timer);
555
+ }
556
+ try {
557
+ const stream = this.logStreams[containerName].stream;
558
+ if (stream) {
559
+ await stream.close();
560
+ }
561
+ }
562
+ catch (err) {
563
+ // Ignore
564
+ }
565
+ delete this.logStreams[containerName];
566
+ }
567
+ }
568
+ async ensureLogListening(systemId, instanceId, handler) {
569
+ const containerName = await (0, utils_1.getBlockInstanceContainerName)(systemId, instanceId);
570
+ try {
571
+ if (this.logStreams[containerName]?.stream) {
572
+ // Already listening - will shut itself down
573
+ return;
574
+ }
575
+ if (this.logStreams[containerName]?.timer) {
576
+ clearTimeout(this.logStreams[containerName].timer);
577
+ }
578
+ const tryLater = () => {
579
+ this.logStreams[containerName] = {
580
+ timer: setTimeout(() => {
581
+ // Keep trying until user decides to not listen anymore
582
+ this.ensureLogListening(systemId, instanceId, handler);
583
+ }, 5000),
584
+ };
585
+ };
586
+ const containerInfo = await this.getContainerByName(containerName);
587
+ if (!containerInfo || !(await containerInfo.isRunning())) {
588
+ // Container not currently running - try again in 5 seconds
589
+ tryLater();
590
+ return;
591
+ }
592
+ const stream = await containerInfo.getLogStream();
593
+ stream.onLog((log) => {
594
+ try {
595
+ handler(log);
596
+ }
597
+ catch (err) {
598
+ console.warn('Error handling log', err);
599
+ }
600
+ });
601
+ stream.onEnd(() => {
602
+ // We get here if the container is stopped
603
+ delete this.logStreams[containerName];
604
+ tryLater();
605
+ });
606
+ stream.onError((err) => {
607
+ // We get here if the container crashes
608
+ delete this.logStreams[containerName];
609
+ tryLater();
610
+ });
611
+ this.logStreams[containerName] = {
612
+ stream,
613
+ };
614
+ }
615
+ catch (err) {
616
+ // Ignore
617
+ }
618
+ }
619
+ buildDockerImage(dockerFile, imageName) {
620
+ const taskName = `Building docker image: ${imageName}`;
621
+ const processor = async (task) => {
622
+ const timeStarted = Date.now();
623
+ const stream = await this.docker().buildImage({
624
+ context: path_1.default.dirname(dockerFile),
625
+ src: [path_1.default.basename(dockerFile)],
626
+ }, {
627
+ t: imageName,
628
+ dockerfile: path_1.default.basename(dockerFile),
629
+ });
630
+ await processJsonStream(`image:build:${imageName}`, stream, (data) => {
631
+ if (data.stream) {
632
+ // Emit raw output to the task log
633
+ task.addLog(data.stream);
634
+ }
635
+ });
636
+ };
637
+ return taskManager_1.taskManager.add(`docker:image:build:${imageName}`, processor, {
638
+ name: taskName,
639
+ });
640
+ }
641
+ }
642
+ function readLogBuffer(logBuffer) {
643
+ const out = [];
644
+ let offset = 0;
645
+ while (offset < logBuffer.length) {
646
+ try {
647
+ // Read the docker log format - explained here:
648
+ // https://docs.docker.com/engine/api/v1.41/#operation/ContainerAttach
649
+ // or here : https://ahmet.im/blog/docker-logs-api-binary-format-explained/
650
+ // First byte is stream type
651
+ const streamTypeInt = logBuffer.readInt8(offset);
652
+ const streamType = streamTypeInt === 1 ? 'stdout' : 'stderr';
653
+ if (streamTypeInt !== 1 && streamTypeInt !== 2) {
654
+ console.error('Unknown stream type: %s', streamTypeInt, out[out.length - 1]);
655
+ break;
656
+ }
657
+ // Bytes 4-8 is frame size
658
+ const messageLength = logBuffer.readInt32BE(offset + 4);
659
+ // After that is the message - with the message length
660
+ const dataWithoutStreamType = logBuffer.subarray(offset + 8, offset + 8 + messageLength);
661
+ const raw = dataWithoutStreamType.toString();
662
+ // Split the message into date and message
663
+ const firstSpaceIx = raw.indexOf(' ');
664
+ const dateString = raw.substring(0, firstSpaceIx);
665
+ const line = raw.substring(firstSpaceIx + 1);
666
+ offset = offset + messageLength + 8;
667
+ if (!dateString) {
668
+ break;
669
+ }
670
+ out.push({
671
+ time: new Date(dateString).getTime(),
672
+ message: line,
673
+ level: 'INFO',
674
+ source: streamType,
675
+ });
676
+ }
677
+ catch (err) {
678
+ console.error('Error parsing log entry', err);
679
+ offset = logBuffer.length;
680
+ break;
681
+ }
682
+ }
683
+ return out;
684
+ }
685
+ class ClosableLogStream {
686
+ stream;
687
+ eventEmitter;
688
+ constructor(stream) {
689
+ this.stream = stream;
690
+ this.eventEmitter = new node_events_1.EventEmitter();
691
+ stream.on('data', (data) => {
692
+ const logs = readLogBuffer(data);
693
+ logs.forEach((log) => {
694
+ this.eventEmitter.emit('log', log);
695
+ });
696
+ });
697
+ stream.on('end', () => {
698
+ this.eventEmitter.emit('end');
699
+ });
700
+ stream.on('error', (error) => {
701
+ this.eventEmitter.emit('error', error);
702
+ });
703
+ stream.on('close', () => {
704
+ this.eventEmitter.emit('end');
705
+ });
706
+ }
707
+ onLog(listener) {
708
+ this.eventEmitter.on('log', listener);
709
+ return () => {
710
+ this.eventEmitter.removeListener('log', listener);
711
+ };
712
+ }
713
+ onEnd(listener) {
714
+ this.eventEmitter.on('end', listener);
715
+ return () => {
716
+ this.eventEmitter.removeListener('end', listener);
717
+ };
718
+ }
719
+ onError(listener) {
720
+ this.eventEmitter.on('error', listener);
721
+ return () => {
722
+ this.eventEmitter.removeListener('error', listener);
723
+ };
724
+ }
725
+ close() {
726
+ return new Promise((resolve, reject) => {
727
+ try {
728
+ this.stream.close((err) => {
729
+ if (err) {
730
+ console.warn('Error closing log stream', err);
731
+ }
732
+ resolve();
733
+ });
734
+ }
735
+ catch (err) {
736
+ // Ignore
737
+ }
738
+ });
739
+ }
740
+ }
741
+ class ContainerInfo {
742
+ _container;
743
+ /**
744
+ *
745
+ * @param {Docker.Container} dockerContainer
746
+ */
747
+ constructor(dockerContainer) {
748
+ /**
749
+ *
750
+ * @type {Docker.Container}
751
+ * @private
752
+ */
753
+ this._container = dockerContainer;
754
+ }
755
+ get native() {
756
+ return this._container;
757
+ }
758
+ async isRunning() {
759
+ const inspectResult = await this.inspect();
760
+ if (!inspectResult || !inspectResult.State) {
761
+ return false;
762
+ }
763
+ return inspectResult.State.Running || inspectResult.State.Restarting;
764
+ }
765
+ async start() {
766
+ if (await this.isRunning()) {
767
+ return;
768
+ }
769
+ await this._container.start();
770
+ }
771
+ async restart() {
772
+ if (!(await this.isRunning())) {
773
+ return this.start();
774
+ }
775
+ await this._container.restart();
776
+ }
777
+ async stop() {
778
+ if (!(await this.isRunning())) {
779
+ return;
780
+ }
781
+ await this._container.stop();
782
+ }
783
+ async remove(opts) {
784
+ await exports.containerManager.remove(this._container, opts);
785
+ }
786
+ async getPort(type) {
787
+ const ports = await this.getPorts();
788
+ if (ports && ports[type]) {
789
+ return ports[type];
790
+ }
791
+ return null;
792
+ }
793
+ async inspect() {
794
+ try {
795
+ return await this._container.inspect();
796
+ }
797
+ catch (err) {
798
+ return undefined;
799
+ }
800
+ }
801
+ async status() {
802
+ const result = await this.inspect();
803
+ return result?.State;
804
+ }
805
+ async getPorts() {
806
+ const inspectResult = await this.inspect();
807
+ if (!inspectResult || !inspectResult.Config || !inspectResult.Config.Labels) {
808
+ return false;
809
+ }
810
+ const portTypes = {};
811
+ const ports = {};
812
+ lodash_1.default.forEach(inspectResult.Config.Labels, (portType, name) => {
813
+ if (!name.startsWith(exports.CONTAINER_LABEL_PORT_PREFIX)) {
814
+ return;
815
+ }
816
+ const hostPort = name.substring(exports.CONTAINER_LABEL_PORT_PREFIX.length);
817
+ portTypes[hostPort] = portType;
818
+ });
819
+ lodash_1.default.forEach(inspectResult.HostConfig.PortBindings, (portBindings, containerPortSpec) => {
820
+ let [containerPort, protocol] = containerPortSpec.split(/\//);
821
+ const hostPort = portBindings[0].HostPort;
822
+ const portType = portTypes[hostPort];
823
+ ports[portType] = {
824
+ containerPort,
825
+ protocol,
826
+ hostPort,
827
+ };
828
+ });
829
+ return ports;
830
+ }
831
+ async getLogStream() {
832
+ try {
833
+ const logStream = (await this.native.logs({
834
+ stdout: true,
835
+ stderr: true,
836
+ follow: true,
837
+ tail: 0,
838
+ timestamps: true,
839
+ }));
840
+ return new ClosableLogStream(logStream);
841
+ }
842
+ catch (err) {
843
+ console.log('Error getting log stream', err);
844
+ throw err;
845
+ }
846
+ }
847
+ async getLogs() {
848
+ const logs = await this.native.logs({
849
+ stdout: true,
850
+ stderr: true,
851
+ follow: false,
852
+ timestamps: true,
853
+ });
854
+ const out = readLogBuffer(logs);
855
+ if (out.length > 0) {
856
+ return out;
857
+ }
858
+ const status = await this.status();
859
+ const healthLogs = status?.Health?.Log
860
+ ? status?.Health?.Log.map((log) => {
861
+ return {
862
+ source: 'stdout',
863
+ level: log.ExitCode === 0 ? 'INFO' : 'ERROR',
864
+ time: Date.now(),
865
+ message: 'Health check: ' + log.Output,
866
+ };
867
+ })
868
+ : [];
869
+ if (status?.Running) {
870
+ return [
871
+ {
872
+ source: 'stdout',
873
+ level: 'INFO',
874
+ time: Date.now(),
875
+ message: 'Container is starting...',
876
+ },
877
+ ...healthLogs,
878
+ ];
879
+ }
880
+ if (status?.Restarting) {
881
+ return [
882
+ {
883
+ source: 'stdout',
884
+ level: 'INFO',
885
+ time: Date.now(),
886
+ message: 'Container is restarting...',
887
+ },
888
+ ...healthLogs,
889
+ ];
890
+ }
891
+ if (status?.Paused) {
892
+ return [
893
+ {
894
+ source: 'stdout',
895
+ level: 'INFO',
896
+ time: Date.now(),
897
+ message: 'Container is paused...',
898
+ },
899
+ ...healthLogs,
900
+ ];
901
+ }
902
+ if (status?.Error) {
903
+ return [
904
+ {
905
+ source: 'stderr',
906
+ level: 'ERROR',
907
+ time: Date.now(),
908
+ message: 'Container failed to start:\n' + status.Error,
909
+ },
910
+ ...healthLogs,
911
+ ];
912
+ }
913
+ return [
914
+ {
915
+ source: 'stdout',
916
+ level: 'INFO',
917
+ time: Date.now(),
918
+ message: 'Container not running',
919
+ ...healthLogs,
920
+ },
921
+ ];
922
+ }
923
+ }
924
+ exports.ContainerInfo = ContainerInfo;
925
+ function getExtraHosts(dockerVersion) {
926
+ if (process.platform !== 'darwin' && process.platform !== 'win32') {
927
+ const [major, minor] = dockerVersion.split('.');
928
+ if (parseInt(major) >= 20 && parseInt(minor) >= 10) {
929
+ // Docker 20.10+ on Linux supports adding host.docker.internal to point to host-gateway
930
+ return [`${types_1.DOCKER_HOST_INTERNAL}:host-gateway`];
931
+ }
932
+ // Docker versions lower than 20.10 needs an actual IP address. We use the default network bridge which
933
+ // is always 172.17.0.1
934
+ return [`${types_1.DOCKER_HOST_INTERNAL}:172.17.0.1`];
935
+ }
936
+ return undefined;
937
+ }
938
+ exports.getExtraHosts = getExtraHosts;
939
+ /**
940
+ * Ensure that the volume is in the correct format for the docker daemon on the host
941
+ *
942
+ * Windows: c:\path\to\volume -> /c/path/to/volume
943
+ * Linux: /path/to/volume -> /path/to/volume
944
+ * Mac: /path/to/volume -> /path/to/volume
945
+ */
946
+ function toLocalBindVolume(volume) {
947
+ if (process.platform === 'win32') {
948
+ //On Windows we need to convert c:\ to /c/
949
+ return volume
950
+ .replace(/^([a-z]):\\/i, (match, drive) => {
951
+ return '/' + drive.toLowerCase() + '/';
952
+ })
953
+ .replace(/\\(\S)/g, '/$1');
954
+ }
955
+ return volume;
956
+ }
957
+ exports.toLocalBindVolume = toLocalBindVolume;
958
+ exports.containerManager = new ContainerManager();