@lakutata/containerd 0.0.0-stage → 3.0.0-beta.1

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 (301) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +409 -2
  3. package/THIRD_PARTY_NOTICES.md +310 -0
  4. package/dist/cjs/client/BuildKit.d.ts +176 -0
  5. package/dist/cjs/client/BuildKit.js +536 -0
  6. package/dist/cjs/client/BuildKitMessages.d.ts +35 -0
  7. package/dist/cjs/client/BuildKitMessages.js +123 -0
  8. package/dist/cjs/client/ContainerStore.d.ts +558 -0
  9. package/dist/cjs/client/ContainerStore.js +1384 -0
  10. package/dist/cjs/client/ContainerdClient.d.ts +244 -0
  11. package/dist/cjs/client/ContainerdClient.js +455 -0
  12. package/dist/cjs/client/DockerIgnore.d.ts +44 -0
  13. package/dist/cjs/client/DockerIgnore.js +186 -0
  14. package/dist/cjs/client/Fifo.d.ts +31 -0
  15. package/dist/cjs/client/Fifo.js +66 -0
  16. package/dist/cjs/client/ImageStore.d.ts +258 -0
  17. package/dist/cjs/client/ImageStore.js +636 -0
  18. package/dist/cjs/client/Messages.d.ts +103 -0
  19. package/dist/cjs/client/Messages.js +397 -0
  20. package/dist/cjs/client/Networks.d.ts +221 -0
  21. package/dist/cjs/client/Networks.js +630 -0
  22. package/dist/cjs/client/Platforms.d.ts +53 -0
  23. package/dist/cjs/client/Platforms.js +169 -0
  24. package/dist/cjs/client/Reference.d.ts +33 -0
  25. package/dist/cjs/client/Reference.js +70 -0
  26. package/dist/cjs/client/Registry.d.ts +145 -0
  27. package/dist/cjs/client/Registry.js +272 -0
  28. package/dist/cjs/client/Rootfs.d.ts +83 -0
  29. package/dist/cjs/client/Rootfs.js +245 -0
  30. package/dist/cjs/client/Seccomp.d.ts +36 -0
  31. package/dist/cjs/client/Seccomp.js +156 -0
  32. package/dist/cjs/client/Spec.d.ts +151 -0
  33. package/dist/cjs/client/Spec.js +227 -0
  34. package/dist/cjs/client/Volumes.d.ts +42 -0
  35. package/dist/cjs/client/Volumes.js +80 -0
  36. package/dist/cjs/containerd/Containerd.d.ts +442 -0
  37. package/dist/cjs/containerd/Containerd.js +929 -0
  38. package/dist/cjs/containerd/exceptions/ContainerdConnectionException.d.ts +9 -0
  39. package/dist/cjs/containerd/exceptions/ContainerdConnectionException.js +16 -0
  40. package/dist/cjs/containerd/exceptions/ContainerdContainerNameConflictException.d.ts +8 -0
  41. package/dist/cjs/containerd/exceptions/ContainerdContainerNameConflictException.js +15 -0
  42. package/dist/cjs/containerd/exceptions/ContainerdContainerNotFoundException.d.ts +9 -0
  43. package/dist/cjs/containerd/exceptions/ContainerdContainerNotFoundException.js +16 -0
  44. package/dist/cjs/containerd/exceptions/ContainerdContainerStateException.d.ts +10 -0
  45. package/dist/cjs/containerd/exceptions/ContainerdContainerStateException.js +17 -0
  46. package/dist/cjs/containerd/exceptions/ContainerdImageBuildException.d.ts +9 -0
  47. package/dist/cjs/containerd/exceptions/ContainerdImageBuildException.js +16 -0
  48. package/dist/cjs/containerd/exceptions/ContainerdImageExportException.d.ts +9 -0
  49. package/dist/cjs/containerd/exceptions/ContainerdImageExportException.js +16 -0
  50. package/dist/cjs/containerd/exceptions/ContainerdImageImportException.d.ts +9 -0
  51. package/dist/cjs/containerd/exceptions/ContainerdImageImportException.js +16 -0
  52. package/dist/cjs/containerd/exceptions/ContainerdImageNotFoundException.d.ts +9 -0
  53. package/dist/cjs/containerd/exceptions/ContainerdImageNotFoundException.js +16 -0
  54. package/dist/cjs/containerd/exceptions/ContainerdImagePullException.d.ts +9 -0
  55. package/dist/cjs/containerd/exceptions/ContainerdImagePullException.js +16 -0
  56. package/dist/cjs/containerd/exceptions/ContainerdImagePushException.d.ts +8 -0
  57. package/dist/cjs/containerd/exceptions/ContainerdImagePushException.js +15 -0
  58. package/dist/cjs/containerd/exceptions/ContainerdNetworkConflictException.d.ts +9 -0
  59. package/dist/cjs/containerd/exceptions/ContainerdNetworkConflictException.js +16 -0
  60. package/dist/cjs/containerd/exceptions/ContainerdNetworkNotFoundException.d.ts +9 -0
  61. package/dist/cjs/containerd/exceptions/ContainerdNetworkNotFoundException.js +16 -0
  62. package/dist/cjs/containerd/exceptions/ContainerdPathNotFoundException.d.ts +8 -0
  63. package/dist/cjs/containerd/exceptions/ContainerdPathNotFoundException.js +15 -0
  64. package/dist/cjs/containerd/exceptions/ContainerdPlatformException.d.ts +9 -0
  65. package/dist/cjs/containerd/exceptions/ContainerdPlatformException.js +16 -0
  66. package/dist/cjs/containerd/exceptions/ContainerdVolumeInUseException.d.ts +8 -0
  67. package/dist/cjs/containerd/exceptions/ContainerdVolumeInUseException.js +15 -0
  68. package/dist/cjs/containerd/exceptions/ContainerdVolumeNotFoundException.d.ts +8 -0
  69. package/dist/cjs/containerd/exceptions/ContainerdVolumeNotFoundException.js +15 -0
  70. package/dist/cjs/containerd/lib/ContainerdContainer.d.ts +292 -0
  71. package/dist/cjs/containerd/lib/ContainerdContainer.js +536 -0
  72. package/dist/cjs/containerd/lib/ContainerdContainerTTY.d.ts +116 -0
  73. package/dist/cjs/containerd/lib/ContainerdContainerTTY.js +178 -0
  74. package/dist/cjs/containerd/lib/ContainerdErrors.d.ts +6 -0
  75. package/dist/cjs/containerd/lib/ContainerdErrors.js +37 -0
  76. package/dist/cjs/containerd/lib/ContainerdImage.d.ts +149 -0
  77. package/dist/cjs/containerd/lib/ContainerdImage.js +222 -0
  78. package/dist/cjs/containerd/options/ContainerdPruneOptions.d.ts +44 -0
  79. package/dist/cjs/containerd/options/ContainerdPruneOptions.js +51 -0
  80. package/dist/cjs/containerd/options/ContainerdRegistryAuthOptions.d.ts +32 -0
  81. package/dist/cjs/containerd/options/ContainerdRegistryAuthOptions.js +45 -0
  82. package/dist/cjs/containerd/options/container/ContainerCommitOptions.d.ts +57 -0
  83. package/dist/cjs/containerd/options/container/ContainerCommitOptions.js +66 -0
  84. package/dist/cjs/containerd/options/container/ContainerCreateOptions.d.ts +273 -0
  85. package/dist/cjs/containerd/options/container/ContainerCreateOptions.js +239 -0
  86. package/dist/cjs/containerd/options/container/ContainerExecOptions.d.ts +53 -0
  87. package/dist/cjs/containerd/options/container/ContainerExecOptions.js +63 -0
  88. package/dist/cjs/containerd/options/container/ContainerExportDirectoryOptions.d.ts +32 -0
  89. package/dist/cjs/containerd/options/container/ContainerExportDirectoryOptions.js +45 -0
  90. package/dist/cjs/containerd/options/container/ContainerKillOptions.d.ts +30 -0
  91. package/dist/cjs/containerd/options/container/ContainerKillOptions.js +41 -0
  92. package/dist/cjs/containerd/options/container/ContainerLogsOptions.d.ts +34 -0
  93. package/dist/cjs/containerd/options/container/ContainerLogsOptions.js +44 -0
  94. package/dist/cjs/containerd/options/container/ContainerRemoveOptions.d.ts +22 -0
  95. package/dist/cjs/containerd/options/container/ContainerRemoveOptions.js +34 -0
  96. package/dist/cjs/containerd/options/container/ContainerResourceOptions.d.ts +63 -0
  97. package/dist/cjs/containerd/options/container/ContainerResourceOptions.js +67 -0
  98. package/dist/cjs/containerd/options/container/ContainerStopOptions.d.ts +30 -0
  99. package/dist/cjs/containerd/options/container/ContainerStopOptions.js +41 -0
  100. package/dist/cjs/containerd/options/container/ContainerTTYConsoleSizeOptions.d.ts +26 -0
  101. package/dist/cjs/containerd/options/container/ContainerTTYConsoleSizeOptions.js +40 -0
  102. package/dist/cjs/containerd/options/container/ContainerTTYOptions.d.ts +50 -0
  103. package/dist/cjs/containerd/options/container/ContainerTTYOptions.js +63 -0
  104. package/dist/cjs/containerd/options/image/ImageBuildOptions.d.ts +88 -0
  105. package/dist/cjs/containerd/options/image/ImageBuildOptions.js +95 -0
  106. package/dist/cjs/containerd/options/image/ImageExportOptions.d.ts +25 -0
  107. package/dist/cjs/containerd/options/image/ImageExportOptions.js +39 -0
  108. package/dist/cjs/containerd/options/image/ImageImportOptions.d.ts +38 -0
  109. package/dist/cjs/containerd/options/image/ImageImportOptions.js +47 -0
  110. package/dist/cjs/containerd/options/image/ImagePullOptions.d.ts +53 -0
  111. package/dist/cjs/containerd/options/image/ImagePullOptions.js +66 -0
  112. package/dist/cjs/containerd/options/image/ImagePushOptions.d.ts +44 -0
  113. package/dist/cjs/containerd/options/image/ImagePushOptions.js +58 -0
  114. package/dist/cjs/containerd/options/network/NetworkCreateOptions.d.ts +82 -0
  115. package/dist/cjs/containerd/options/network/NetworkCreateOptions.js +83 -0
  116. package/dist/cjs/containerd/options/volume/VolumeCreateOptions.d.ts +26 -0
  117. package/dist/cjs/containerd/options/volume/VolumeCreateOptions.js +40 -0
  118. package/dist/cjs/containerd/types/ContainerdBind.d.ts +28 -0
  119. package/dist/cjs/containerd/types/ContainerdBind.js +2 -0
  120. package/dist/cjs/containerd/types/ContainerdBuildOutputCallback.d.ts +6 -0
  121. package/dist/cjs/containerd/types/ContainerdBuildOutputCallback.js +2 -0
  122. package/dist/cjs/containerd/types/ContainerdContainerNetwork.d.ts +40 -0
  123. package/dist/cjs/containerd/types/ContainerdContainerNetwork.js +2 -0
  124. package/dist/cjs/containerd/types/ContainerdContainerPort.d.ts +22 -0
  125. package/dist/cjs/containerd/types/ContainerdContainerPort.js +2 -0
  126. package/dist/cjs/containerd/types/ContainerdContainerState.d.ts +42 -0
  127. package/dist/cjs/containerd/types/ContainerdContainerState.js +2 -0
  128. package/dist/cjs/containerd/types/ContainerdContainerStats.d.ts +107 -0
  129. package/dist/cjs/containerd/types/ContainerdContainerStats.js +2 -0
  130. package/dist/cjs/containerd/types/ContainerdDevice.d.ts +25 -0
  131. package/dist/cjs/containerd/types/ContainerdDevice.js +2 -0
  132. package/dist/cjs/containerd/types/ContainerdExecResult.d.ts +18 -0
  133. package/dist/cjs/containerd/types/ContainerdExecResult.js +2 -0
  134. package/dist/cjs/containerd/types/ContainerdImageConfig.d.ts +46 -0
  135. package/dist/cjs/containerd/types/ContainerdImageConfig.js +2 -0
  136. package/dist/cjs/containerd/types/ContainerdNetworkInfo.d.ts +49 -0
  137. package/dist/cjs/containerd/types/ContainerdNetworkInfo.js +2 -0
  138. package/dist/cjs/containerd/types/ContainerdProgressCallback.d.ts +29 -0
  139. package/dist/cjs/containerd/types/ContainerdProgressCallback.js +2 -0
  140. package/dist/cjs/containerd/types/ContainerdPruneResult.d.ts +21 -0
  141. package/dist/cjs/containerd/types/ContainerdPruneResult.js +2 -0
  142. package/dist/cjs/containerd/types/ContainerdRestartPolicy.d.ts +13 -0
  143. package/dist/cjs/containerd/types/ContainerdRestartPolicy.js +2 -0
  144. package/dist/cjs/containerd/types/ContainerdTmpfs.d.ts +20 -0
  145. package/dist/cjs/containerd/types/ContainerdTmpfs.js +2 -0
  146. package/dist/cjs/containerd/types/ContainerdVolume.d.ts +19 -0
  147. package/dist/cjs/containerd/types/ContainerdVolume.js +2 -0
  148. package/dist/cjs/containerd/types/ContainerdVolumeInfo.d.ts +26 -0
  149. package/dist/cjs/containerd/types/ContainerdVolumeInfo.js +2 -0
  150. package/dist/cjs/exports/Containerd.d.ts +69 -0
  151. package/dist/cjs/exports/Containerd.js +87 -0
  152. package/dist/cjs/package.json +1 -0
  153. package/dist/esm/client/BuildKit.js +488 -0
  154. package/dist/esm/client/BuildKitMessages.js +120 -0
  155. package/dist/esm/client/ContainerStore.js +1339 -0
  156. package/dist/esm/client/ContainerdClient.js +416 -0
  157. package/dist/esm/client/DockerIgnore.js +179 -0
  158. package/dist/esm/client/Fifo.js +59 -0
  159. package/dist/esm/client/ImageStore.js +628 -0
  160. package/dist/esm/client/Messages.js +393 -0
  161. package/dist/esm/client/Networks.js +621 -0
  162. package/dist/esm/client/Platforms.js +156 -0
  163. package/dist/esm/client/Reference.js +66 -0
  164. package/dist/esm/client/Registry.js +263 -0
  165. package/dist/esm/client/Rootfs.js +234 -0
  166. package/dist/esm/client/Seccomp.js +152 -0
  167. package/dist/esm/client/Spec.js +220 -0
  168. package/dist/esm/client/Volumes.js +73 -0
  169. package/dist/esm/containerd/Containerd.js +923 -0
  170. package/dist/esm/containerd/exceptions/ContainerdConnectionException.js +12 -0
  171. package/dist/esm/containerd/exceptions/ContainerdContainerNameConflictException.js +11 -0
  172. package/dist/esm/containerd/exceptions/ContainerdContainerNotFoundException.js +12 -0
  173. package/dist/esm/containerd/exceptions/ContainerdContainerStateException.js +13 -0
  174. package/dist/esm/containerd/exceptions/ContainerdImageBuildException.js +12 -0
  175. package/dist/esm/containerd/exceptions/ContainerdImageExportException.js +12 -0
  176. package/dist/esm/containerd/exceptions/ContainerdImageImportException.js +12 -0
  177. package/dist/esm/containerd/exceptions/ContainerdImageNotFoundException.js +12 -0
  178. package/dist/esm/containerd/exceptions/ContainerdImagePullException.js +12 -0
  179. package/dist/esm/containerd/exceptions/ContainerdImagePushException.js +11 -0
  180. package/dist/esm/containerd/exceptions/ContainerdNetworkConflictException.js +12 -0
  181. package/dist/esm/containerd/exceptions/ContainerdNetworkNotFoundException.js +12 -0
  182. package/dist/esm/containerd/exceptions/ContainerdPathNotFoundException.js +11 -0
  183. package/dist/esm/containerd/exceptions/ContainerdPlatformException.js +12 -0
  184. package/dist/esm/containerd/exceptions/ContainerdVolumeInUseException.js +11 -0
  185. package/dist/esm/containerd/exceptions/ContainerdVolumeNotFoundException.js +11 -0
  186. package/dist/esm/containerd/lib/ContainerdContainer.js +533 -0
  187. package/dist/esm/containerd/lib/ContainerdContainerTTY.js +175 -0
  188. package/dist/esm/containerd/lib/ContainerdErrors.js +34 -0
  189. package/dist/esm/containerd/lib/ContainerdImage.js +219 -0
  190. package/dist/esm/containerd/options/ContainerdPruneOptions.js +47 -0
  191. package/dist/esm/containerd/options/ContainerdRegistryAuthOptions.js +41 -0
  192. package/dist/esm/containerd/options/container/ContainerCommitOptions.js +62 -0
  193. package/dist/esm/containerd/options/container/ContainerCreateOptions.js +235 -0
  194. package/dist/esm/containerd/options/container/ContainerExecOptions.js +59 -0
  195. package/dist/esm/containerd/options/container/ContainerExportDirectoryOptions.js +41 -0
  196. package/dist/esm/containerd/options/container/ContainerKillOptions.js +37 -0
  197. package/dist/esm/containerd/options/container/ContainerLogsOptions.js +40 -0
  198. package/dist/esm/containerd/options/container/ContainerRemoveOptions.js +30 -0
  199. package/dist/esm/containerd/options/container/ContainerResourceOptions.js +63 -0
  200. package/dist/esm/containerd/options/container/ContainerStopOptions.js +37 -0
  201. package/dist/esm/containerd/options/container/ContainerTTYConsoleSizeOptions.js +36 -0
  202. package/dist/esm/containerd/options/container/ContainerTTYOptions.js +59 -0
  203. package/dist/esm/containerd/options/image/ImageBuildOptions.js +91 -0
  204. package/dist/esm/containerd/options/image/ImageExportOptions.js +35 -0
  205. package/dist/esm/containerd/options/image/ImageImportOptions.js +43 -0
  206. package/dist/esm/containerd/options/image/ImagePullOptions.js +62 -0
  207. package/dist/esm/containerd/options/image/ImagePushOptions.js +54 -0
  208. package/dist/esm/containerd/options/network/NetworkCreateOptions.js +79 -0
  209. package/dist/esm/containerd/options/volume/VolumeCreateOptions.js +36 -0
  210. package/dist/esm/containerd/types/ContainerdBind.js +1 -0
  211. package/dist/esm/containerd/types/ContainerdBuildOutputCallback.js +1 -0
  212. package/dist/esm/containerd/types/ContainerdContainerNetwork.js +1 -0
  213. package/dist/esm/containerd/types/ContainerdContainerPort.js +1 -0
  214. package/dist/esm/containerd/types/ContainerdContainerState.js +1 -0
  215. package/dist/esm/containerd/types/ContainerdContainerStats.js +1 -0
  216. package/dist/esm/containerd/types/ContainerdDevice.js +1 -0
  217. package/dist/esm/containerd/types/ContainerdExecResult.js +1 -0
  218. package/dist/esm/containerd/types/ContainerdImageConfig.js +1 -0
  219. package/dist/esm/containerd/types/ContainerdNetworkInfo.js +1 -0
  220. package/dist/esm/containerd/types/ContainerdProgressCallback.js +1 -0
  221. package/dist/esm/containerd/types/ContainerdPruneResult.js +1 -0
  222. package/dist/esm/containerd/types/ContainerdRestartPolicy.js +1 -0
  223. package/dist/esm/containerd/types/ContainerdTmpfs.js +1 -0
  224. package/dist/esm/containerd/types/ContainerdVolume.js +1 -0
  225. package/dist/esm/containerd/types/ContainerdVolumeInfo.js +1 -0
  226. package/dist/esm/exports/Containerd.js +68 -0
  227. package/dist/types/client/BuildKit.d.ts +176 -0
  228. package/dist/types/client/BuildKitMessages.d.ts +35 -0
  229. package/dist/types/client/ContainerStore.d.ts +558 -0
  230. package/dist/types/client/ContainerdClient.d.ts +244 -0
  231. package/dist/types/client/DockerIgnore.d.ts +44 -0
  232. package/dist/types/client/Fifo.d.ts +31 -0
  233. package/dist/types/client/ImageStore.d.ts +258 -0
  234. package/dist/types/client/Messages.d.ts +103 -0
  235. package/dist/types/client/Networks.d.ts +221 -0
  236. package/dist/types/client/Platforms.d.ts +53 -0
  237. package/dist/types/client/Reference.d.ts +33 -0
  238. package/dist/types/client/Registry.d.ts +145 -0
  239. package/dist/types/client/Rootfs.d.ts +83 -0
  240. package/dist/types/client/Seccomp.d.ts +36 -0
  241. package/dist/types/client/Spec.d.ts +151 -0
  242. package/dist/types/client/Volumes.d.ts +42 -0
  243. package/dist/types/containerd/Containerd.d.ts +442 -0
  244. package/dist/types/containerd/exceptions/ContainerdConnectionException.d.ts +9 -0
  245. package/dist/types/containerd/exceptions/ContainerdContainerNameConflictException.d.ts +8 -0
  246. package/dist/types/containerd/exceptions/ContainerdContainerNotFoundException.d.ts +9 -0
  247. package/dist/types/containerd/exceptions/ContainerdContainerStateException.d.ts +10 -0
  248. package/dist/types/containerd/exceptions/ContainerdImageBuildException.d.ts +9 -0
  249. package/dist/types/containerd/exceptions/ContainerdImageExportException.d.ts +9 -0
  250. package/dist/types/containerd/exceptions/ContainerdImageImportException.d.ts +9 -0
  251. package/dist/types/containerd/exceptions/ContainerdImageNotFoundException.d.ts +9 -0
  252. package/dist/types/containerd/exceptions/ContainerdImagePullException.d.ts +9 -0
  253. package/dist/types/containerd/exceptions/ContainerdImagePushException.d.ts +8 -0
  254. package/dist/types/containerd/exceptions/ContainerdNetworkConflictException.d.ts +9 -0
  255. package/dist/types/containerd/exceptions/ContainerdNetworkNotFoundException.d.ts +9 -0
  256. package/dist/types/containerd/exceptions/ContainerdPathNotFoundException.d.ts +8 -0
  257. package/dist/types/containerd/exceptions/ContainerdPlatformException.d.ts +9 -0
  258. package/dist/types/containerd/exceptions/ContainerdVolumeInUseException.d.ts +8 -0
  259. package/dist/types/containerd/exceptions/ContainerdVolumeNotFoundException.d.ts +8 -0
  260. package/dist/types/containerd/lib/ContainerdContainer.d.ts +292 -0
  261. package/dist/types/containerd/lib/ContainerdContainerTTY.d.ts +116 -0
  262. package/dist/types/containerd/lib/ContainerdErrors.d.ts +6 -0
  263. package/dist/types/containerd/lib/ContainerdImage.d.ts +149 -0
  264. package/dist/types/containerd/options/ContainerdPruneOptions.d.ts +44 -0
  265. package/dist/types/containerd/options/ContainerdRegistryAuthOptions.d.ts +32 -0
  266. package/dist/types/containerd/options/container/ContainerCommitOptions.d.ts +57 -0
  267. package/dist/types/containerd/options/container/ContainerCreateOptions.d.ts +273 -0
  268. package/dist/types/containerd/options/container/ContainerExecOptions.d.ts +53 -0
  269. package/dist/types/containerd/options/container/ContainerExportDirectoryOptions.d.ts +32 -0
  270. package/dist/types/containerd/options/container/ContainerKillOptions.d.ts +30 -0
  271. package/dist/types/containerd/options/container/ContainerLogsOptions.d.ts +34 -0
  272. package/dist/types/containerd/options/container/ContainerRemoveOptions.d.ts +22 -0
  273. package/dist/types/containerd/options/container/ContainerResourceOptions.d.ts +63 -0
  274. package/dist/types/containerd/options/container/ContainerStopOptions.d.ts +30 -0
  275. package/dist/types/containerd/options/container/ContainerTTYConsoleSizeOptions.d.ts +26 -0
  276. package/dist/types/containerd/options/container/ContainerTTYOptions.d.ts +50 -0
  277. package/dist/types/containerd/options/image/ImageBuildOptions.d.ts +88 -0
  278. package/dist/types/containerd/options/image/ImageExportOptions.d.ts +25 -0
  279. package/dist/types/containerd/options/image/ImageImportOptions.d.ts +38 -0
  280. package/dist/types/containerd/options/image/ImagePullOptions.d.ts +53 -0
  281. package/dist/types/containerd/options/image/ImagePushOptions.d.ts +44 -0
  282. package/dist/types/containerd/options/network/NetworkCreateOptions.d.ts +82 -0
  283. package/dist/types/containerd/options/volume/VolumeCreateOptions.d.ts +26 -0
  284. package/dist/types/containerd/types/ContainerdBind.d.ts +28 -0
  285. package/dist/types/containerd/types/ContainerdBuildOutputCallback.d.ts +6 -0
  286. package/dist/types/containerd/types/ContainerdContainerNetwork.d.ts +40 -0
  287. package/dist/types/containerd/types/ContainerdContainerPort.d.ts +22 -0
  288. package/dist/types/containerd/types/ContainerdContainerState.d.ts +42 -0
  289. package/dist/types/containerd/types/ContainerdContainerStats.d.ts +107 -0
  290. package/dist/types/containerd/types/ContainerdDevice.d.ts +25 -0
  291. package/dist/types/containerd/types/ContainerdExecResult.d.ts +18 -0
  292. package/dist/types/containerd/types/ContainerdImageConfig.d.ts +46 -0
  293. package/dist/types/containerd/types/ContainerdNetworkInfo.d.ts +49 -0
  294. package/dist/types/containerd/types/ContainerdProgressCallback.d.ts +29 -0
  295. package/dist/types/containerd/types/ContainerdPruneResult.d.ts +21 -0
  296. package/dist/types/containerd/types/ContainerdRestartPolicy.d.ts +13 -0
  297. package/dist/types/containerd/types/ContainerdTmpfs.d.ts +20 -0
  298. package/dist/types/containerd/types/ContainerdVolume.d.ts +19 -0
  299. package/dist/types/containerd/types/ContainerdVolumeInfo.d.ts +26 -0
  300. package/dist/types/exports/Containerd.d.ts +69 -0
  301. package/package.json +51 -4
@@ -0,0 +1,1339 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { availableParallelism, constants as osConstants, hostname as osHostname, totalmem } from 'node:os';
3
+ import { copyFile, lstat, mkdir, open, readdir, readFile, readlink, rm, stat, truncate, writeFile } from 'node:fs/promises';
4
+ import { createReadStream, existsSync, readdirSync } from 'node:fs';
5
+ import { pipeline } from 'node:stream/promises';
6
+ import { pack as tarPack } from 'tar-stream';
7
+ import path from 'node:path';
8
+ import { Readable } from 'node:stream';
9
+ import { setTimeout as sleep } from 'node:timers/promises';
10
+ import { ContainerdClient, isGRPCError, PROCESS_TYPE_URL, SPEC_TYPE_URL, timestampDate } from './ContainerdClient.js';
11
+ import { GRPCStatus, decode, encode } from '@lakutata/grpc';
12
+ import * as M from './Messages.js';
13
+ import { formatPlatform, hostPlatform, parsePlatform } from './Platforms.js';
14
+ import { chainIds } from './ImageStore.js';
15
+ import { ProcessFifo } from './Fifo.js';
16
+ import { VolumeStore } from './Volumes.js';
17
+ import { DEFAULT_NETWORK } from './Networks.js';
18
+ import { DirectoryTree, layerDirectories, LayersTree, resolveUser } from './Rootfs.js';
19
+ import { containerCapabilities, execProcessSpec, generateSpec, parseDeviceCgroupRule } from './Spec.js';
20
+ import { defaultSeccompProfile, seccompSupported } from './Seccomp.js';
21
+ /**
22
+ * The labels of the containers: the ones of nerdctl (nerdctl ps, nerdctl logs know them), the ones of the restart
23
+ * monitor of containerd
24
+ */
25
+ export const ContainerLabels = {
26
+ NAME: 'nerdctl/name',
27
+ HOSTNAME: 'nerdctl/hostname',
28
+ PLATFORM: 'nerdctl/platform',
29
+ LOG_URI: 'nerdctl/log-uri',
30
+ STATE_DIR: 'nerdctl/state-dir',
31
+ NETWORKS: 'nerdctl/networks',
32
+ PORTS: 'nerdctl/ports',
33
+ MOUNTS: 'nerdctl/mounts',
34
+ STOP_SIGNAL: 'io.containerd.image.config.stop-signal',
35
+ RESTART_POLICY: 'containerd.io/restart.policy',
36
+ RESTART_STATUS: 'containerd.io/restart.status',
37
+ RESTART_LOG_URI: 'containerd.io/restart.loguri'
38
+ };
39
+ /**
40
+ * The resolv.conf of a container: the one of the host, without its local name servers on its own network (the ones of
41
+ * Google when it has no other one, as Docker does)
42
+ * @param content
43
+ * @param ownNetwork
44
+ */
45
+ export function resolvConf(content, ownNetwork) {
46
+ if (!ownNetwork)
47
+ return content;
48
+ const lines = content.split('\n').filter((line) => !/^\s*nameserver\s+(127\.|::1\s*$)/.test(line));
49
+ while (lines.length && !lines[lines.length - 1].trim())
50
+ lines.pop();
51
+ if (!lines.some((line) => /^\s*nameserver\s+/.test(line)))
52
+ lines.push('nameserver 8.8.8.8', 'nameserver 8.8.4.4');
53
+ return lines.join('\n') + '\n';
54
+ }
55
+ /**
56
+ * An error of the runtime refusing the profile of seccomp of a container (a runc built without seccomp), with what to do
57
+ * @param error
58
+ */
59
+ export function seccompHint(error) {
60
+ const message = error?.message ?? '';
61
+ if (!/seccomp/i.test(message) || !/not (supported|enabled)/i.test(message))
62
+ return error;
63
+ return Object.assign(error, { message: `${message} (the runtime of this machine does not filter the system calls with seccomp: create the container with the profile of seccomp unconfined)` });
64
+ }
65
+ /**
66
+ * The number of a signal (SIGTERM, TERM, 15)
67
+ * @param signal
68
+ */
69
+ export function signalNumber(signal) {
70
+ if (typeof signal === 'number')
71
+ return signal;
72
+ if (/^\d+$/.test(signal))
73
+ return Number(signal);
74
+ const name = signal.toUpperCase().startsWith('SIG') ? signal.toUpperCase() : `SIG${signal.toUpperCase()}`;
75
+ const number = osConstants.signals[name];
76
+ if (number === undefined)
77
+ throw new Error(`The signal ${signal} is unknown`);
78
+ return number;
79
+ }
80
+ let swapLimitSupported;
81
+ /**
82
+ * Whether the kernel limits the swap of the cgroups (Docker discards the swap limits otherwise): the files of the swap
83
+ * limits, in the cgroups below the root on cgroup v2 (the root has none)
84
+ */
85
+ export function isSwapLimitSupported() {
86
+ if (swapLimitSupported !== undefined)
87
+ return swapLimitSupported;
88
+ if (existsSync('/sys/fs/cgroup/cgroup.controllers')) {
89
+ try {
90
+ swapLimitSupported = readdirSync('/sys/fs/cgroup', { withFileTypes: true }).some((entry) => entry.isDirectory() && existsSync(path.join('/sys/fs/cgroup', entry.name, 'memory.swap.max')));
91
+ }
92
+ catch {
93
+ swapLimitSupported = false;
94
+ }
95
+ }
96
+ else {
97
+ swapLimitSupported = existsSync('/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes');
98
+ }
99
+ return swapLimitSupported;
100
+ }
101
+ /**
102
+ * The swap limit of a container (its memory and its swap together), as Docker and nerdctl give it: twice its memory
103
+ * limit by default, -1 unlimited; none without a memory limit, or when the kernel does not limit the swap
104
+ * @param memory
105
+ * @param memorySwap
106
+ */
107
+ export function swapLimit(memory, memorySwap) {
108
+ if (!memory || !isSwapLimitSupported())
109
+ return undefined;
110
+ return memorySwap === -1 || (memorySwap && memorySwap > 0) ? memorySwap : memory * 2;
111
+ }
112
+ /**
113
+ * The CPUs of the machine online, as a cpuset writes them (0-3,6)
114
+ */
115
+ async function onlineCpuSet() {
116
+ return (await readFile('/sys/devices/system/cpu/online', 'utf8').catch(() => '')).trim() || `0-${availableParallelism() - 1}`;
117
+ }
118
+ /**
119
+ * The counters of a metrics message as numbers: the 64-bit ones beyond 2^53 (an unlimited limit, 2^64 - 1) are decoded
120
+ * as bigints, the statistics are numbers
121
+ * @param value
122
+ */
123
+ function metricCounters(value) {
124
+ if (typeof value === 'bigint')
125
+ return Number(value);
126
+ if (Array.isArray(value))
127
+ return value.map(metricCounters);
128
+ if (value && typeof value === 'object' && !(value instanceof Uint8Array))
129
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, metricCounters(item)]));
130
+ return value;
131
+ }
132
+ /**
133
+ * The containers of containerd: created from the images (their specifications of OCI, their snapshots), their tasks
134
+ * started, stopped, executed in; their logs written by the shims of containerd (file://), kept by the restart monitor
135
+ * of containerd as their restart policies ask
136
+ */
137
+ export class ContainerStore {
138
+ constructor(client, images, networks, options, volumes) {
139
+ this.client = client;
140
+ this.images = images;
141
+ this.networks = networks;
142
+ this.options = options;
143
+ this.volumes = volumes ?? new VolumeStore(path.join(options.dataRoot, 'volumes', client.namespace));
144
+ }
145
+ /**
146
+ * The names of the volumes of a container
147
+ * @param container
148
+ */
149
+ static Volumes(container) {
150
+ try {
151
+ return JSON.parse(container.labels?.[ContainerLabels.MOUNTS] ?? '[]')
152
+ .filter((mount) => mount.Type === 'volume' && !!mount.Name)
153
+ .map((mount) => mount.Name);
154
+ }
155
+ catch {
156
+ return [];
157
+ }
158
+ }
159
+ /**
160
+ * The networks of CNI of a container
161
+ * @param network
162
+ * @protected
163
+ */
164
+ async cniNetworks(network) {
165
+ if (!Array.isArray(network))
166
+ return [];
167
+ if (!network.length)
168
+ throw new Error('The networks of the container are empty');
169
+ const networks = [];
170
+ for (const item of network) {
171
+ if (item.name === 'host' || item.name === 'none')
172
+ throw new Error(`The network ${item.name} is not one of CNI: give it alone`);
173
+ const found = await this.networks.get(item.name);
174
+ if (!found)
175
+ throw Object.assign(new Error(`The network ${item.name} does not exist`), { code: 'NETWORK_NOT_FOUND' });
176
+ if (networks.some((known) => known.network.name === found.name))
177
+ throw new Error(`The network ${found.name} is given twice`);
178
+ networks.push({ network: found, ip: item.ip, ipv6: item.ipv6 });
179
+ }
180
+ return networks;
181
+ }
182
+ /**
183
+ * The profile of seccomp of a container, as nerdctl gives it: none for the privileged ones, unconfined ones and when
184
+ * the kernel does not filter the system calls; the default one by its capabilities; the one of a file
185
+ * @param options
186
+ * @protected
187
+ */
188
+ async seccomp(options) {
189
+ const profile = options.seccompProfile ?? 'default';
190
+ if (options.privileged || profile === 'unconfined')
191
+ return undefined;
192
+ if (profile !== 'default') {
193
+ try {
194
+ return JSON.parse(await readFile(profile, 'utf8'));
195
+ }
196
+ catch (error) {
197
+ throw new Error(`The profile of seccomp ${profile} is not read: ${error.message}`, { cause: error });
198
+ }
199
+ }
200
+ if (!seccompSupported())
201
+ return undefined;
202
+ return defaultSeccompProfile(containerCapabilities(options));
203
+ }
204
+ /**
205
+ * The interfaces of a container on its networks of CNI (their addresses while it runs)
206
+ * @param container
207
+ */
208
+ async interfaces(container) {
209
+ return await this.networks.interfaces(this.stateDir(container.id));
210
+ }
211
+ /**
212
+ * The directory of the state of a container
213
+ * @param id
214
+ */
215
+ stateDir(id) {
216
+ return path.join(this.options.dataRoot, 'containers', this.client.namespace, id);
217
+ }
218
+ /**
219
+ * The file of the logs of a container
220
+ * @param id
221
+ */
222
+ logFile(id) {
223
+ return path.join(this.stateDir(id), `${id}.log`);
224
+ }
225
+ /**
226
+ * The state written of a container
227
+ * @param id
228
+ */
229
+ async readState(id) {
230
+ try {
231
+ return JSON.parse(await readFile(path.join(this.stateDir(id), 'state.json'), 'utf8'));
232
+ }
233
+ catch {
234
+ return {};
235
+ }
236
+ }
237
+ async writeState(id, state) {
238
+ await mkdir(this.stateDir(id), { recursive: true });
239
+ await writeFile(path.join(this.stateDir(id), 'state.json'), JSON.stringify({ ...await this.readState(id), ...state }));
240
+ }
241
+ /**
242
+ * The settings a container was created with by the component (its update creates it again with them)
243
+ * @param id
244
+ */
245
+ async readSettings(id) {
246
+ try {
247
+ return JSON.parse(await readFile(path.join(this.stateDir(id), 'settings.json'), 'utf8'));
248
+ }
249
+ catch {
250
+ return undefined;
251
+ }
252
+ }
253
+ async writeSettings(id, settings) {
254
+ await mkdir(this.stateDir(id), { recursive: true });
255
+ await writeFile(path.join(this.stateDir(id), 'settings.json'), JSON.stringify(settings));
256
+ }
257
+ /**
258
+ * Name a container again (its label of nerdctl)
259
+ * @param container
260
+ * @param name
261
+ */
262
+ async rename(container, name) {
263
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name))
264
+ throw new Error(`The name of a container "${name}" is not valid ([a-zA-Z0-9][a-zA-Z0-9_.-]*)`);
265
+ const used = await this.client.listContainers([`labels."${ContainerLabels.NAME}"==${name}`]);
266
+ if (used.some((other) => other.id !== container.id))
267
+ throw Object.assign(new Error(`The name ${name} is used by another container`), { code: 'CONFLICT' });
268
+ await this.label(container, { [ContainerLabels.NAME]: name });
269
+ }
270
+ /**
271
+ * Find a container: by its identifier, by its name, by a prefix of its identifier (only one)
272
+ * @param idOrName
273
+ */
274
+ async find(idOrName) {
275
+ const byId = await this.client.getContainer(idOrName);
276
+ if (byId)
277
+ return byId;
278
+ const byName = await this.client.listContainers([`labels."${ContainerLabels.NAME}"==${idOrName}`]);
279
+ if (byName.length)
280
+ return byName[0];
281
+ if (!/^[0-9a-f]{3,}$/.test(idOrName))
282
+ return undefined;
283
+ const byPrefix = (await this.client.listContainers()).filter((container) => container.id.startsWith(idOrName));
284
+ if (byPrefix.length > 1)
285
+ throw new Error(`The prefix ${idOrName} names ${byPrefix.length} containers`);
286
+ return byPrefix[0];
287
+ }
288
+ /**
289
+ * The containers
290
+ * @param filters the filters of containerd (labels."app"==web)
291
+ */
292
+ async list(filters = []) {
293
+ return await this.client.listContainers(filters);
294
+ }
295
+ /**
296
+ * The devices of a host path (a device, or the ones of a directory)
297
+ * @param mapping
298
+ */
299
+ async devices(mapping) {
300
+ const stats = await stat(mapping.hostPath, { bigint: true });
301
+ const containerPath = mapping.containerPath ?? mapping.hostPath;
302
+ if (stats.isDirectory()) {
303
+ const devices = [];
304
+ for (const entry of await readdir(mapping.hostPath)) {
305
+ devices.push(...await this.devices({ hostPath: path.join(mapping.hostPath, entry), containerPath: path.posix.join(containerPath, entry), permissions: mapping.permissions }).catch(() => []));
306
+ }
307
+ return devices;
308
+ }
309
+ if (!stats.isCharacterDevice() && !stats.isBlockDevice())
310
+ throw new Error(`${mapping.hostPath} is not a device`);
311
+ return [ContainerStore.Device(mapping.hostPath, containerPath, stats, mapping.permissions ?? 'rwm')];
312
+ }
313
+ /**
314
+ * A device of the host, by the stats of its node (its major and minor numbers of its rdev, as glibc splits it)
315
+ * @param hostPath
316
+ * @param containerPath
317
+ * @param stats
318
+ * @param permissions
319
+ */
320
+ static Device(hostPath, containerPath, stats, permissions) {
321
+ const rdev = stats.rdev;
322
+ return {
323
+ path: hostPath,
324
+ containerPath: containerPath,
325
+ type: stats.isCharacterDevice() ? 'c' : 'b',
326
+ major: Number(((rdev >> 8n) & 0xfffn) | ((rdev >> 32n) & ~0xfffn)),
327
+ minor: Number((rdev & 0xffn) | ((rdev >> 12n) & ~0xffn)),
328
+ fileMode: Number(stats.mode & 511n),
329
+ uid: Number(stats.uid),
330
+ gid: Number(stats.gid),
331
+ permissions: permissions
332
+ };
333
+ }
334
+ /**
335
+ * The devices of the host given to a privileged container, as containerd's WithHostDevices gives them: the
336
+ * character and block devices of /dev and of its subdirectories, without the links, the console and the directories
337
+ * of the pseudo terminals, the shared memory, the descriptors and the queues
338
+ * @param directory
339
+ */
340
+ async hostDevices(directory = '/dev') {
341
+ const devices = [];
342
+ const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
343
+ for (const entry of entries) {
344
+ const entryPath = path.join(directory, entry.name);
345
+ if (entry.isDirectory()) {
346
+ if (!['pts', 'shm', 'fd', 'mqueue', '.lxc', '.lxd-mounts', '.udev'].includes(entry.name))
347
+ devices.push(...await this.hostDevices(entryPath));
348
+ continue;
349
+ }
350
+ if (entry.name === 'console' || (!entry.isCharacterDevice() && !entry.isBlockDevice()))
351
+ continue;
352
+ //Removed meanwhile
353
+ const stats = await lstat(entryPath, { bigint: true }).catch(() => undefined);
354
+ if (stats && (stats.isCharacterDevice() || stats.isBlockDevice()))
355
+ devices.push(ContainerStore.Device(entryPath, entryPath, stats, 'rwm'));
356
+ }
357
+ return devices;
358
+ }
359
+ /**
360
+ * The arguments of the process of a container: its entrypoint and its command, as Docker composes them
361
+ * @param resolved
362
+ * @param options
363
+ */
364
+ args(resolved, options) {
365
+ const config = resolved.config.config ?? {};
366
+ const entrypoint = options.entrypoint ?? config.Entrypoint ?? [];
367
+ //An entrypoint given resets the command of the image
368
+ const cmd = options.cmd ?? (options.entrypoint ? [] : config.Cmd ?? []);
369
+ const args = [...entrypoint.filter((part, index) => !(index === 0 && part === '' && entrypoint.length === 1)), ...cmd];
370
+ if (!args.length)
371
+ throw new Error(`The image ${resolved.name} has no command: give the command of the container`);
372
+ return args;
373
+ }
374
+ /**
375
+ * Create a container of an image: its snapshot (its root file system, the image unpacked), its specification, its
376
+ * state directory (its hosts, resolv.conf, hostname, logs)
377
+ * @param options
378
+ */
379
+ async create(options) {
380
+ const platform = options.platform ?? hostPlatform();
381
+ const resolved = await this.images.resolve(options.image, platform);
382
+ const id = randomBytes(32).toString('hex');
383
+ const name = options.name ?? `${resolved.name.split('/').pop().split(/[:@]/)[0].replace(/[^a-zA-Z0-9_.-]/g, '')}-${id.slice(0, 5)}`;
384
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name))
385
+ throw new Error(`The name of a container "${name}" is not valid ([a-zA-Z0-9][a-zA-Z0-9_.-]*)`);
386
+ if ((await this.client.listContainers([`labels."${ContainerLabels.NAME}"==${name}`])).length)
387
+ throw Object.assign(new Error(`The name ${name} is used by another container`), { code: 'CONFLICT' });
388
+ const network = Array.isArray(options.network) ? 'cni' : options.network ?? 'host';
389
+ const cniNetworks = await this.cniNetworks(options.network);
390
+ const ports = options.ports ?? [];
391
+ if (ports.length && network !== 'cni')
392
+ throw new Error(`The ports of a container are published on its networks of CNI (${DEFAULT_NETWORK}), not on the network ${network}`);
393
+ const hostname = options.hostname ?? (network === 'host' ? osHostname() : id.slice(0, 12));
394
+ const lease = await this.client.createLease();
395
+ const stateDir = this.stateDir(id);
396
+ try {
397
+ const chain = chainIds(resolved.config.rootfs?.diff_ids ?? []);
398
+ const parent = chain[chain.length - 1] ?? '';
399
+ if (parent && !(await this.client.statSnapshot(this.options.snapshotter, parent)))
400
+ await this.images.unpack(resolved.manifest, lease);
401
+ const mounts = await this.client.prepareSnapshot(this.options.snapshotter, id, parent, {}, lease);
402
+ const user = await resolveUser(mounts, options.user ?? resolved.config.config?.User);
403
+ //The environment: the one of the image, PATH, HOSTNAME, HOME, the one given
404
+ const env = new Map([['PATH', '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin']]);
405
+ for (const entry of resolved.config.config?.Env ?? [])
406
+ env.set(entry.split('=')[0], entry.slice(entry.indexOf('=') + 1));
407
+ env.set('HOSTNAME', hostname);
408
+ if (!env.has('HOME'))
409
+ env.set('HOME', user.home);
410
+ for (const [key, value] of Object.entries(options.env ?? {}))
411
+ env.set(key, value);
412
+ if (options.terminal && !env.has('TERM'))
413
+ env.set('TERM', 'xterm');
414
+ //The files of the network of the container
415
+ await mkdir(stateDir, { recursive: true });
416
+ const hostHosts = network === 'host' ? await readFile('/etc/hosts', 'utf8').catch(() => '') : '';
417
+ const hosts = [hostHosts.trimEnd() || '127.0.0.1\tlocalhost localhost.localdomain\n::1\tlocalhost localhost.localdomain ip6-localhost ip6-loopback'];
418
+ if (network === 'none')
419
+ hosts.push(`127.0.0.1\t${hostname}`);
420
+ for (const [host, address] of Object.entries(options.extraHosts ?? {}))
421
+ hosts.push(`${address}\t${host}`);
422
+ //The hosts of the networks of CNI: the address of the container added by its hook
423
+ await writeFile(path.join(stateDir, 'hosts.base'), hosts.join('\n') + '\n');
424
+ await writeFile(path.join(stateDir, 'hosts'), hosts.join('\n') + '\n');
425
+ await writeFile(path.join(stateDir, 'resolv.conf'), options.dns?.length ? options.dns.map((server) => `nameserver ${server}`).join('\n') + '\n' : resolvConf(await readFile('/etc/resolv.conf', 'utf8').catch(() => ''), network !== 'host'));
426
+ await writeFile(path.join(stateDir, 'hostname'), hostname + '\n');
427
+ await writeFile(this.logFile(id), '');
428
+ const specMounts = [
429
+ { source: path.join(stateDir, 'hosts'), destination: '/etc/hosts', options: ['bind', 'rprivate'] },
430
+ { source: path.join(stateDir, 'resolv.conf'), destination: '/etc/resolv.conf', options: ['bind', 'rprivate'] },
431
+ { source: path.join(stateDir, 'hostname'), destination: '/etc/hostname', options: ['bind', 'rprivate'] }
432
+ ];
433
+ for (const mount of options.mounts ?? []) {
434
+ if (mount.type === 'tmpfs') {
435
+ specMounts.push({ type: 'tmpfs', source: 'tmpfs', destination: mount.target, options: ['nosuid', 'nodev', 'noexec', ...mount.options ?? [], ...mount.readonly ? ['ro'] : []] });
436
+ }
437
+ else if (mount.type === 'volume') {
438
+ if (!mount.source)
439
+ throw new Error(`The volume mounted on ${mount.target} has no name`);
440
+ const volume = await this.volumes.create(mount.source);
441
+ specMounts.push({ type: 'bind', source: volume.mountpoint, destination: mount.target, options: ['rbind', 'rprivate', mount.readonly ? 'ro' : 'rw'] });
442
+ }
443
+ else {
444
+ if (!mount.source)
445
+ throw new Error(`The bind mount of ${mount.target} has no source`);
446
+ const source = path.resolve(mount.source);
447
+ await mkdir(source, { recursive: true }).catch(() => undefined);
448
+ specMounts.push({ type: 'bind', source: source, destination: mount.target, options: ['rbind', ...mount.options?.length ? mount.options : ['rprivate'], mount.readonly ? 'ro' : 'rw'] });
449
+ }
450
+ }
451
+ const devices = [];
452
+ for (const device of options.devices ?? [])
453
+ devices.push(...await this.devices(device));
454
+ //The devices of the host in its /dev (its cgroup allows them all), the ones given kept
455
+ if (options.privileged)
456
+ devices.push(...(await this.hostDevices()).filter((device) => !devices.some((given) => given.containerPath === device.containerPath)));
457
+ let hooks;
458
+ if (network === 'cni') {
459
+ if (!this.networks.pluginDirectories().length)
460
+ throw new Error(`The plugins of CNI are not installed (${this.networks.cniPath.join(', ')}): install them (the package containernetworking-plugins, or the CNI plugins release in /opt/cni/bin), or give the container the network 'host'`);
461
+ await this.networks.render(stateDir, id, cniNetworks, ports);
462
+ const hook = path.join(stateDir, 'network', 'hook.sh');
463
+ hooks = {
464
+ createRuntime: [{ path: '/bin/sh', args: ['sh', hook, 'add', stateDir], timeout: 120 }],
465
+ poststop: [{ path: '/bin/sh', args: ['sh', hook, 'del', stateDir], timeout: 120 }]
466
+ };
467
+ }
468
+ const spec = generateSpec({
469
+ id: id,
470
+ namespace: this.client.namespace,
471
+ args: this.args(resolved, options),
472
+ env: [...env].map(([key, value]) => `${key}=${value}`),
473
+ cwd: options.workingDir ?? resolved.config.config?.WorkingDir ?? '/',
474
+ uid: user.uid,
475
+ gid: user.gid,
476
+ additionalGids: user.additionalGids,
477
+ hostname: hostname,
478
+ terminal: options.terminal,
479
+ privileged: options.privileged,
480
+ readonlyRootfs: options.readonlyRootfs,
481
+ capAdd: options.capAdd,
482
+ capDrop: options.capDrop,
483
+ seccomp: await this.seccomp(options),
484
+ mounts: specMounts,
485
+ devices: devices,
486
+ deviceCgroupRules: (options.deviceCgroupRules ?? []).map(parseDeviceCgroupRule),
487
+ network: { mode: network },
488
+ hooks: hooks,
489
+ pidHost: options.pidHost,
490
+ ipcHost: options.ipcHost,
491
+ cgroupNamespace: (options.cgroupns ?? 'private') === 'private' && existsSync('/sys/fs/cgroup/cgroup.controllers'),
492
+ cgroupsPath: this.options.cgroupManager === 'systemd' ? `system.slice:lakutata:${id}` : `/${this.client.namespace}/${id}`,
493
+ memory: options.memory,
494
+ memorySwap: swapLimit(options.memory, options.memorySwap),
495
+ cpuQuota: options.cpus ? Math.round(options.cpus * 100000) : undefined,
496
+ cpuShares: options.cpuShares,
497
+ cpuset: options.cpuset,
498
+ pidsLimit: options.pidsLimit,
499
+ oomKillDisable: options.oomKillDisable,
500
+ sysctls: options.sysctls,
501
+ shmSize: options.shmSize
502
+ });
503
+ const logUri = `file://${this.logFile(id)}`;
504
+ const labels = {
505
+ ...resolved.config.config?.Labels ?? {},
506
+ ...options.labels ?? {},
507
+ [ContainerLabels.NAME]: name,
508
+ [ContainerLabels.HOSTNAME]: hostname,
509
+ [ContainerLabels.PLATFORM]: formatPlatform(resolved.platform),
510
+ [ContainerLabels.LOG_URI]: logUri,
511
+ [ContainerLabels.STATE_DIR]: stateDir,
512
+ [ContainerLabels.NETWORKS]: JSON.stringify(network === 'cni' ? cniNetworks.map((item) => item.network.name) : [network]),
513
+ [ContainerLabels.PORTS]: JSON.stringify(ports.map((port) => ({ HostPort: port.hostPort, ContainerPort: port.containerPort, Protocol: port.protocol, HostIP: port.hostIP || '0.0.0.0' }))),
514
+ [ContainerLabels.MOUNTS]: JSON.stringify((options.mounts ?? []).map((mount) => mount.type === 'volume'
515
+ ? { Type: 'volume', Name: mount.source, Source: path.join(this.volumes.directory, mount.source, '_data'), Destination: mount.target, RW: !mount.readonly }
516
+ : { Type: mount.type ?? 'bind', Source: mount.source ?? '', Destination: mount.target, RW: !mount.readonly }))
517
+ };
518
+ const stopSignal = options.stopSignal ?? resolved.config.config?.StopSignal;
519
+ if (stopSignal)
520
+ labels[ContainerLabels.STOP_SIGNAL] = stopSignal;
521
+ if (options.restart && options.restart !== 'no') {
522
+ labels[ContainerLabels.RESTART_POLICY] = options.restart;
523
+ labels[ContainerLabels.RESTART_LOG_URI] = logUri;
524
+ labels[ContainerLabels.RESTART_STATUS] = 'stopped';
525
+ }
526
+ const runtime = { name: this.options.runtime };
527
+ if (this.options.cgroupManager === 'systemd')
528
+ runtime.options = { typeUrl: 'containerd.runc.v1.Options', value: encode(M.RuncOptions, { systemdCgroup: true }) };
529
+ return await this.client.createContainer({
530
+ id: id,
531
+ labels: labels,
532
+ image: resolved.name,
533
+ runtime: runtime,
534
+ spec: ContainerdClient.Json(SPEC_TYPE_URL, spec),
535
+ snapshotter: this.options.snapshotter,
536
+ snapshotKey: id
537
+ }, lease);
538
+ }
539
+ catch (error) {
540
+ await this.client.removeSnapshot(this.options.snapshotter, id).catch(() => undefined);
541
+ await rm(stateDir, { recursive: true, force: true });
542
+ throw error;
543
+ }
544
+ finally {
545
+ await this.client.deleteLease(lease);
546
+ }
547
+ }
548
+ /**
549
+ * The state of a container: the one of its task, its last exit when it has none
550
+ * @param container
551
+ */
552
+ async state(container) {
553
+ const stored = await this.readState(container.id);
554
+ const task = await this.client.getTask(container.id);
555
+ const restarting = container.labels?.[ContainerLabels.RESTART_STATUS] === 'running';
556
+ const state = {
557
+ status: 'created',
558
+ running: false,
559
+ paused: false,
560
+ pid: 0,
561
+ exitCode: stored.exitCode ?? 0,
562
+ startedAt: stored.startedAt ? new Date(stored.startedAt) : null,
563
+ finishedAt: stored.finishedAt ? new Date(stored.finishedAt) : null
564
+ };
565
+ if (!task) {
566
+ state.status = stored.startedAt ? 'exited' : 'created';
567
+ //Its restart monitor recreates its task
568
+ if (restarting && stored.startedAt)
569
+ state.status = 'restarting';
570
+ return state;
571
+ }
572
+ state.pid = task.pid ?? 0;
573
+ switch (task.status) {
574
+ case M.ProcessStatus.RUNNING:
575
+ state.status = 'running';
576
+ state.running = true;
577
+ break;
578
+ case M.ProcessStatus.PAUSED:
579
+ state.status = 'paused';
580
+ state.paused = true;
581
+ break;
582
+ case M.ProcessStatus.PAUSING:
583
+ state.status = 'pausing';
584
+ break;
585
+ case M.ProcessStatus.STOPPED:
586
+ state.status = restarting ? 'restarting' : 'exited';
587
+ state.exitCode = task.exitStatus ?? 0;
588
+ state.finishedAt = timestampDate(task.exitedAt) ?? state.finishedAt;
589
+ break;
590
+ case M.ProcessStatus.CREATED:
591
+ state.status = 'created';
592
+ break;
593
+ default:
594
+ state.status = 'unknown';
595
+ }
596
+ return state;
597
+ }
598
+ /**
599
+ * Rotate the logs of a container once they are larger than the size of the options: copied to the file .1, the
600
+ * file truncated (the shim writes to its end)
601
+ * @param id
602
+ */
603
+ async rotateLogs(id) {
604
+ if (!this.options.logMaxSize)
605
+ return;
606
+ const file = this.logFile(id);
607
+ const size = (await stat(file).catch(() => undefined))?.size ?? 0;
608
+ if (size <= this.options.logMaxSize)
609
+ return;
610
+ await copyFile(file, `${file}.1`);
611
+ await truncate(file, 0);
612
+ }
613
+ /**
614
+ * Delete the task of a container (stopped), its exit kept
615
+ * @param id
616
+ */
617
+ async deleteTask(id) {
618
+ const deleted = await this.client.deleteTask(id).catch(async (error) => {
619
+ //A task still running (a stop of its restart monitor meanwhile): killed
620
+ if (!isGRPCError(error, GRPCStatus.FAILED_PRECONDITION))
621
+ throw error;
622
+ await this.client.kill(id, signalNumber('SIGKILL'), { all: true }).catch(() => undefined);
623
+ await this.client.wait(id).catch(() => undefined);
624
+ return await this.client.deleteTask(id);
625
+ });
626
+ if (deleted)
627
+ await this.writeState(id, { exitCode: deleted.exitStatus ?? 0, finishedAt: (timestampDate(deleted.exitedAt) ?? new Date()).toISOString() });
628
+ }
629
+ /**
630
+ * Set the labels of a container
631
+ * @param container
632
+ * @param labels
633
+ */
634
+ async label(container, labels) {
635
+ await this.client.updateContainer({ id: container.id, labels: labels }, Object.keys(labels).map((key) => `labels.${key}`));
636
+ }
637
+ /**
638
+ * Start a container: a new task of its snapshot (the one of its previous run deleted), its output written to its
639
+ * logs
640
+ * @param container
641
+ */
642
+ async start(container) {
643
+ const task = await this.client.getTask(container.id);
644
+ if (task?.status === M.ProcessStatus.RUNNING)
645
+ return task.pid;
646
+ if (task?.status === M.ProcessStatus.PAUSED)
647
+ throw Object.assign(new Error(`The container ${container.id} is paused: resume it`), { code: 'PAUSED' });
648
+ if (task)
649
+ await this.deleteTask(container.id);
650
+ await this.rotateLogs(container.id);
651
+ const logUri = container.labels?.[ContainerLabels.LOG_URI] ?? `file://${this.logFile(container.id)}`;
652
+ await mkdir(path.dirname(this.logFile(container.id)), { recursive: true });
653
+ const rootfs = await this.client.snapshotMounts(container.snapshotter || this.options.snapshotter, container.snapshotKey || container.id);
654
+ await this.client.createTask({ containerId: container.id, rootfs: rootfs, stdout: logUri, stderr: logUri }).catch((error) => {
655
+ throw seccompHint(error);
656
+ });
657
+ try {
658
+ const pid = await this.client.startTask(container.id).catch((error) => {
659
+ throw seccompHint(error);
660
+ });
661
+ await this.writeState(container.id, { startedAt: new Date().toISOString(), finishedAt: undefined, exitCode: undefined });
662
+ if (container.labels?.[ContainerLabels.RESTART_POLICY])
663
+ await this.label(container, { [ContainerLabels.RESTART_STATUS]: 'running' });
664
+ return pid;
665
+ }
666
+ catch (error) {
667
+ await this.client.deleteTask(container.id).catch(() => undefined);
668
+ throw error;
669
+ }
670
+ }
671
+ /**
672
+ * Wait for the task of a container to exit, at most a time
673
+ * @param id
674
+ * @param timeout milliseconds
675
+ */
676
+ async waitExit(id, timeout) {
677
+ const abort = new AbortController();
678
+ const timer = setTimeout(() => abort.abort(), timeout);
679
+ try {
680
+ await this.client.wait(id, '', abort.signal);
681
+ return true;
682
+ }
683
+ catch (error) {
684
+ if (abort.signal.aborted)
685
+ return false;
686
+ if (isGRPCError(error, GRPCStatus.NOT_FOUND))
687
+ return true;
688
+ throw error;
689
+ }
690
+ finally {
691
+ clearTimeout(timer);
692
+ }
693
+ }
694
+ /**
695
+ * Stop a container: its stop signal (SIGTERM, the one of its image) sent, then SIGKILL to all its processes after a
696
+ * time; its task deleted, its restart monitor told not to restart it
697
+ * @param container
698
+ * @param options
699
+ */
700
+ async stop(container, options = {}) {
701
+ if (container.labels?.[ContainerLabels.RESTART_POLICY])
702
+ await this.label(container, { [ContainerLabels.RESTART_STATUS]: 'stopped' });
703
+ const task = await this.client.getTask(container.id);
704
+ if (!task)
705
+ return;
706
+ if (task.status === M.ProcessStatus.PAUSED)
707
+ await this.client.resume(container.id);
708
+ if (task.status === M.ProcessStatus.RUNNING || task.status === M.ProcessStatus.PAUSED) {
709
+ const signal = signalNumber(options.signal ?? container.labels?.[ContainerLabels.STOP_SIGNAL] ?? 'SIGTERM');
710
+ await this.client.kill(container.id, signal).catch((error) => {
711
+ if (!isGRPCError(error, GRPCStatus.NOT_FOUND))
712
+ throw error;
713
+ });
714
+ if (!(await this.waitExit(container.id, (options.timeout ?? 10) * 1000))) {
715
+ await this.client.kill(container.id, signalNumber('SIGKILL'), { all: true }).catch(() => undefined);
716
+ await this.waitExit(container.id, 10000);
717
+ }
718
+ }
719
+ await this.deleteTask(container.id);
720
+ }
721
+ /**
722
+ * Send a signal to a container (its main process, or all its processes)
723
+ * @param container
724
+ * @param signal
725
+ * @param all
726
+ */
727
+ async kill(container, signal = 'SIGKILL', all = false) {
728
+ if (signalNumber(signal) === signalNumber('SIGKILL') && container.labels?.[ContainerLabels.RESTART_POLICY])
729
+ await this.label(container, { [ContainerLabels.RESTART_STATUS]: 'stopped' });
730
+ await this.client.kill(container.id, signalNumber(signal), { all: all }).catch((error) => {
731
+ throw this.taskError(container, error);
732
+ });
733
+ }
734
+ async pause(container) {
735
+ await this.running(container);
736
+ await this.client.pause(container.id).catch((error) => {
737
+ throw this.taskError(container, error);
738
+ });
739
+ }
740
+ async resume(container) {
741
+ await this.running(container);
742
+ await this.client.resume(container.id).catch((error) => {
743
+ throw this.taskError(container, error);
744
+ });
745
+ }
746
+ /**
747
+ * Check that a container has a task which did not exit (the shim refuses to pause or resume a stopped one with an
748
+ * error of no code)
749
+ * @param container
750
+ * @protected
751
+ */
752
+ async running(container) {
753
+ const task = await this.client.getTask(container.id);
754
+ if (!task || task.status === M.ProcessStatus.STOPPED)
755
+ throw Object.assign(new Error(`The container ${ContainerStore.Name(container)} is not running`), { code: 'NOT_RUNNING' });
756
+ }
757
+ /**
758
+ * The processes of a running container (their identifiers on the host)
759
+ * @param container
760
+ */
761
+ async processes(container) {
762
+ return await this.client.listPids(container.id).catch((error) => {
763
+ throw this.taskError(container, error);
764
+ });
765
+ }
766
+ /**
767
+ * Wait for the main process of a container to exit: its exit code; the last one at once when it has no task (never
768
+ * started: 0, stopped: its task deleted, its exit kept by the store)
769
+ * @param container
770
+ * @param signal
771
+ */
772
+ async wait(container, signal) {
773
+ try {
774
+ return (await this.client.wait(container.id, '', signal)).exitStatus;
775
+ }
776
+ catch (error) {
777
+ if (!isGRPCError(error, GRPCStatus.NOT_FOUND))
778
+ throw error;
779
+ return (await this.readState(container.id)).exitCode ?? 0;
780
+ }
781
+ }
782
+ /**
783
+ * The error of an operation on the task of a container: the container is not running when it has no task (or its
784
+ * process exited)
785
+ * @param container
786
+ * @param error
787
+ * @protected
788
+ */
789
+ taskError(container, error) {
790
+ if (!isGRPCError(error, GRPCStatus.NOT_FOUND))
791
+ return error;
792
+ return Object.assign(new Error(`The container ${ContainerStore.Name(container)} is not running (${error.message})`, { cause: error }), { code: 'NOT_RUNNING' });
793
+ }
794
+ /**
795
+ * Remove a container: its task (a running one only when forced), its snapshot, its state directory
796
+ * @param container
797
+ * @param force
798
+ */
799
+ async remove(container, force = false) {
800
+ const task = await this.client.getTask(container.id);
801
+ if (task && task.status !== M.ProcessStatus.STOPPED && task.status !== M.ProcessStatus.CREATED) {
802
+ if (!force)
803
+ throw Object.assign(new Error(`The container ${ContainerStore.Name(container)} is running: stop it or remove it by force`), { code: 'RUNNING' });
804
+ if (container.labels?.[ContainerLabels.RESTART_POLICY])
805
+ await this.label(container, { [ContainerLabels.RESTART_STATUS]: 'stopped' });
806
+ if (task.status === M.ProcessStatus.PAUSED)
807
+ await this.client.resume(container.id).catch(() => undefined);
808
+ await this.client.kill(container.id, signalNumber('SIGKILL'), { all: true }).catch(() => undefined);
809
+ await this.waitExit(container.id, 10000);
810
+ }
811
+ if (task)
812
+ await this.client.deleteTask(container.id).catch(() => undefined);
813
+ //The networks its hook did not delete
814
+ await this.networks.teardown(this.stateDir(container.id));
815
+ await this.client.deleteContainer(container.id);
816
+ await this.client.removeSnapshot(container.snapshotter || this.options.snapshotter, container.snapshotKey || container.id);
817
+ await rm(this.stateDir(container.id), { recursive: true, force: true });
818
+ }
819
+ /**
820
+ * Execute a command in a running container, its output captured: its standard output and its error apart (FIFOs,
821
+ * mkfifo of the system), or together in its standard output without mkfifo (the files of the shim)
822
+ * @param container
823
+ * @param options
824
+ */
825
+ async exec(container, options) {
826
+ const task = await this.client.getTask(container.id);
827
+ if (task?.status !== M.ProcessStatus.RUNNING)
828
+ throw Object.assign(new Error(`The container ${ContainerStore.Name(container)} is not running`), { code: 'NOT_RUNNING' });
829
+ const spec = ContainerdClient.FromJson(container.spec) ?? {};
830
+ let user;
831
+ if (options.user)
832
+ user = await resolveUser(await this.client.snapshotMounts(container.snapshotter || this.options.snapshotter, container.snapshotKey || container.id), options.user);
833
+ const process = execProcessSpec(spec, {
834
+ args: options.cmd,
835
+ env: Object.entries(options.env ?? {}).map(([key, value]) => `${key}=${value}`),
836
+ cwd: options.workingDir,
837
+ uid: user?.uid,
838
+ gid: user?.gid,
839
+ additionalGids: user?.additionalGids
840
+ });
841
+ const execId = `exec-${randomBytes(8).toString('hex')}`;
842
+ const directory = path.join(this.stateDir(container.id), 'exec');
843
+ await mkdir(directory, { recursive: true });
844
+ const files = [];
845
+ const fifos = [];
846
+ const fifo = ProcessFifo.Supported();
847
+ if (!fifo && options.stdin !== undefined)
848
+ throw new Error('The standard input of an execution needs mkfifo');
849
+ try {
850
+ let stdout, stderr, stdin;
851
+ let request;
852
+ if (fifo) {
853
+ stdout = ProcessFifo.Create(path.join(directory, `${execId}-stdout`), 'read');
854
+ stderr = ProcessFifo.Create(path.join(directory, `${execId}-stderr`), 'read');
855
+ fifos.push(stdout, stderr);
856
+ if (options.stdin !== undefined)
857
+ fifos.push(stdin = ProcessFifo.Create(path.join(directory, `${execId}-stdin`), 'write'));
858
+ request = { stdout: stdout.path, stderr: stderr.path, stdin: stdin?.path ?? '' };
859
+ }
860
+ else {
861
+ const output = path.join(directory, `${execId}.log`);
862
+ files.push(output);
863
+ await writeFile(output, '');
864
+ request = { stdout: `file://${output}`, stderr: `file://${output}` };
865
+ }
866
+ files.push(...fifos.map((item) => item.path));
867
+ await this.client.exec({ containerId: container.id, execId: execId, ...request, spec: ContainerdClient.Json(PROCESS_TYPE_URL, process) });
868
+ const waiting = this.client.wait(container.id, execId);
869
+ waiting.catch(() => undefined);
870
+ await this.client.startTask(container.id, execId);
871
+ const read = (item) => {
872
+ if (!item)
873
+ return Promise.resolve('');
874
+ const socket = item.open();
875
+ const chunks = [];
876
+ return new Promise((resolve) => {
877
+ socket.on('data', (chunk) => chunks.push(chunk));
878
+ socket.on('error', () => resolve(Buffer.concat(chunks).toString('utf8')));
879
+ socket.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
880
+ });
881
+ };
882
+ const outputs = Promise.all([read(stdout), read(stderr)]);
883
+ if (stdin) {
884
+ //Written and closed, then the writer of the shim closed (CloseIO): the end of the input once read
885
+ const writer = stdin.open();
886
+ await new Promise((resolve) => void writer.end(options.stdin ?? '', () => resolve()));
887
+ await this.client.closeIO(container.id, execId).catch(() => undefined);
888
+ }
889
+ const timer = options.timeout ? setTimeout(() => {
890
+ this.client.kill(container.id, signalNumber('SIGKILL'), { execId: execId }).catch(() => undefined);
891
+ }, options.timeout) : undefined;
892
+ try {
893
+ const { exitStatus } = await waiting;
894
+ if (!fifo)
895
+ return { exitCode: exitStatus, stdout: await readFile(files[0], 'utf8'), stderr: '' };
896
+ const [out, err] = await outputs;
897
+ return { exitCode: exitStatus, stdout: out, stderr: err };
898
+ }
899
+ finally {
900
+ clearTimeout(timer);
901
+ }
902
+ }
903
+ finally {
904
+ fifos.forEach((item) => item.close());
905
+ await this.client.deleteProcess(container.id, execId).catch(() => undefined);
906
+ for (const file of files)
907
+ await rm(file, { force: true });
908
+ }
909
+ }
910
+ /**
911
+ * The logs of a container: its last lines, followed while it runs (until the signal aborts)
912
+ * @param container
913
+ * @param options
914
+ */
915
+ logs(container, options = {}) {
916
+ const file = this.logFile(container.id);
917
+ const store = this;
918
+ return Readable.from((async function* () {
919
+ let content = Buffer.concat([await readFile(`${file}.1`).catch(() => Buffer.alloc(0)), await readFile(file).catch(() => Buffer.alloc(0))]);
920
+ let offset = (await stat(file).catch(() => undefined))?.size ?? 0;
921
+ if (options.tail !== undefined && options.tail >= 0) {
922
+ let end = content.length;
923
+ if (content[end - 1] === 0x0a)
924
+ end--;
925
+ let start = end;
926
+ for (let lines = 0; start > 0; start--) {
927
+ if (content[start - 1] === 0x0a && ++lines >= options.tail)
928
+ break;
929
+ }
930
+ content = options.tail === 0 ? Buffer.alloc(0) : content.subarray(start);
931
+ }
932
+ if (content.length)
933
+ yield content;
934
+ if (!options.follow)
935
+ return;
936
+ let idle = 0;
937
+ while (!options.signal?.aborted) {
938
+ const size = (await stat(file).catch(() => undefined))?.size ?? 0;
939
+ //Rotated: read from its start
940
+ if (size < offset)
941
+ offset = 0;
942
+ if (size > offset) {
943
+ const handle = await open(file, 'r');
944
+ try {
945
+ const buffer = Buffer.alloc(size - offset);
946
+ await handle.read(buffer, 0, buffer.length, offset);
947
+ offset = size;
948
+ yield buffer;
949
+ }
950
+ finally {
951
+ await handle.close();
952
+ }
953
+ continue;
954
+ }
955
+ //Every second, whether it still runs
956
+ if (++idle % 4 === 0) {
957
+ const task = await store.client.getTask(container.id);
958
+ //Stopped, and not restarted by its restart monitor
959
+ if ((!task || task.status === M.ProcessStatus.STOPPED) && (await store.client.getContainer(container.id))?.labels?.[ContainerLabels.RESTART_STATUS] !== 'running')
960
+ break;
961
+ }
962
+ await sleep(250, undefined, { signal: options.signal }).catch(() => undefined);
963
+ }
964
+ })());
965
+ }
966
+ /**
967
+ * A sample of the resources a running container uses
968
+ * @param container
969
+ * @protected
970
+ */
971
+ async sample(container) {
972
+ const task = await this.client.getTask(container.id);
973
+ if (task?.status !== M.ProcessStatus.RUNNING && task?.status !== M.ProcessStatus.PAUSED)
974
+ throw Object.assign(new Error(`The container ${ContainerStore.Name(container)} is not running`), { code: 'NOT_RUNNING' });
975
+ const metric = await this.client.metrics(container.id);
976
+ const type = String(metric?.data?.typeUrl ?? '');
977
+ const data = metric?.data?.value ?? new Uint8Array();
978
+ const unlimited = (value) => !value || value >= 2 ** 62 ? totalmem() : value;
979
+ const onlineCpus = availableParallelism();
980
+ const stats = {
981
+ read: timestampDate(metric?.timestamp) ?? new Date(),
982
+ pids: { current: 0, limit: 0 },
983
+ cpu: { usage: 0, user: 0, system: 0, throttledPeriods: 0, throttledTime: 0, onlineCpus: onlineCpus },
984
+ memory: { usage: 0, workingSet: 0, limit: totalmem(), cache: 0, swap: 0, oomKills: 0 },
985
+ blkio: { readBytes: 0, writeBytes: 0, readOps: 0, writeOps: 0 },
986
+ networks: {}
987
+ };
988
+ if (type.endsWith('cgroups.v2.Metrics')) {
989
+ const metrics = metricCounters(decode(M.CgroupV2Metrics, data));
990
+ stats.pids = { current: metrics.pids?.current ?? 0, limit: metrics.pids?.limit ?? 0 };
991
+ stats.cpu = {
992
+ usage: (metrics.cpu?.usageUsec ?? 0) * 1000,
993
+ user: (metrics.cpu?.userUsec ?? 0) * 1000,
994
+ system: (metrics.cpu?.systemUsec ?? 0) * 1000,
995
+ throttledPeriods: metrics.cpu?.nrThrottled ?? 0,
996
+ throttledTime: (metrics.cpu?.throttledUsec ?? 0) * 1000,
997
+ onlineCpus: onlineCpus
998
+ };
999
+ const usage = metrics.memory?.usage ?? 0;
1000
+ stats.memory = {
1001
+ usage: usage,
1002
+ workingSet: Math.max(0, usage - (metrics.memory?.inactiveFile ?? 0)),
1003
+ limit: unlimited(metrics.memory?.usageLimit ?? 0),
1004
+ cache: metrics.memory?.file ?? 0,
1005
+ swap: metrics.memory?.swapUsage ?? 0,
1006
+ oomKills: metrics.memoryEvents?.oomKill ?? 0
1007
+ };
1008
+ for (const entry of metrics.io?.usage ?? []) {
1009
+ stats.blkio.readBytes += entry.rbytes ?? 0;
1010
+ stats.blkio.writeBytes += entry.wbytes ?? 0;
1011
+ stats.blkio.readOps += entry.rios ?? 0;
1012
+ stats.blkio.writeOps += entry.wios ?? 0;
1013
+ }
1014
+ }
1015
+ else if (type.endsWith('cgroups.v1.Metrics')) {
1016
+ const metrics = metricCounters(decode(M.CgroupV1Metrics, data));
1017
+ stats.pids = { current: metrics.pids?.current ?? 0, limit: metrics.pids?.limit ?? 0 };
1018
+ stats.cpu = {
1019
+ usage: metrics.cpu?.usage?.total ?? 0,
1020
+ user: metrics.cpu?.usage?.user ?? 0,
1021
+ system: metrics.cpu?.usage?.kernel ?? 0,
1022
+ throttledPeriods: metrics.cpu?.throttling?.throttledPeriods ?? 0,
1023
+ throttledTime: metrics.cpu?.throttling?.throttledTime ?? 0,
1024
+ onlineCpus: metrics.cpu?.usage?.perCpu?.length || onlineCpus
1025
+ };
1026
+ const usage = metrics.memory?.usage?.usage ?? 0;
1027
+ stats.memory = {
1028
+ usage: usage,
1029
+ workingSet: Math.max(0, usage - (metrics.memory?.totalInactiveFile ?? 0)),
1030
+ limit: unlimited(metrics.memory?.usage?.limit ?? 0),
1031
+ cache: metrics.memory?.cache ?? 0,
1032
+ swap: Math.max(0, (metrics.memory?.swap?.usage ?? 0) - usage),
1033
+ oomKills: metrics.memoryOomControl?.oomKill ?? 0
1034
+ };
1035
+ for (const entry of metrics.blkio?.ioServiceBytesRecursive ?? []) {
1036
+ if (/^read$/i.test(entry.op))
1037
+ stats.blkio.readBytes += entry.value ?? 0;
1038
+ if (/^write$/i.test(entry.op))
1039
+ stats.blkio.writeBytes += entry.value ?? 0;
1040
+ }
1041
+ for (const entry of metrics.blkio?.ioServicedRecursive ?? []) {
1042
+ if (/^read$/i.test(entry.op))
1043
+ stats.blkio.readOps += entry.value ?? 0;
1044
+ if (/^write$/i.test(entry.op))
1045
+ stats.blkio.writeOps += entry.value ?? 0;
1046
+ }
1047
+ }
1048
+ //The interfaces of its namespace of network (without its loopback)
1049
+ try {
1050
+ for (const line of (await readFile(`/proc/${task.pid}/net/dev`, 'utf8')).split('\n').slice(2)) {
1051
+ const [name, counters] = line.split(':');
1052
+ if (!counters || name.trim() === 'lo')
1053
+ continue;
1054
+ const values = counters.trim().split(/\s+/).map(Number);
1055
+ stats.networks[name.trim()] = {
1056
+ rxBytes: values[0], rxPackets: values[1], rxErrors: values[2], rxDropped: values[3],
1057
+ txBytes: values[8], txPackets: values[9], txErrors: values[10], txDropped: values[11]
1058
+ };
1059
+ }
1060
+ }
1061
+ catch {
1062
+ //Its process is gone
1063
+ }
1064
+ return stats;
1065
+ }
1066
+ /**
1067
+ * The resources a running container uses: two samples, its use of the CPUs between them
1068
+ * @param container
1069
+ * @param interval milliseconds between the samples
1070
+ */
1071
+ async stats(container, interval = 1000) {
1072
+ const first = await this.sample(container);
1073
+ if (interval <= 0)
1074
+ return { ...first, cpu: { ...first.cpu, percent: 0 } };
1075
+ await sleep(interval);
1076
+ const second = await this.sample(container);
1077
+ const elapsed = (second.read.getTime() - first.read.getTime()) * 1e6 || interval * 1e6;
1078
+ return { ...second, cpu: { ...second.cpu, percent: Math.max(0, (second.cpu.usage - first.cpu.usage) / elapsed * 100) } };
1079
+ }
1080
+ /**
1081
+ * Open a terminal in a running container: a process executed with its pseudo terminal, its input and its output by
1082
+ * FIFOs (mkfifo of the system)
1083
+ * @param container
1084
+ * @param options
1085
+ */
1086
+ async terminal(container, options) {
1087
+ const task = await this.client.getTask(container.id);
1088
+ if (task?.status !== M.ProcessStatus.RUNNING)
1089
+ throw Object.assign(new Error(`The container ${ContainerStore.Name(container)} is not running`), { code: 'NOT_RUNNING' });
1090
+ if (!ProcessFifo.Supported())
1091
+ throw new Error('The terminals of the containers need mkfifo');
1092
+ const spec = ContainerdClient.FromJson(container.spec) ?? {};
1093
+ let user;
1094
+ if (options.user)
1095
+ user = await resolveUser(await this.client.snapshotMounts(container.snapshotter || this.options.snapshotter, container.snapshotKey || container.id), options.user);
1096
+ const env = { TERM: 'xterm', ...options.env ?? {} };
1097
+ const process = execProcessSpec(spec, {
1098
+ args: options.cmd,
1099
+ env: Object.entries(env).map(([key, value]) => `${key}=${value}`),
1100
+ cwd: options.workingDir,
1101
+ uid: user?.uid,
1102
+ gid: user?.gid,
1103
+ additionalGids: user?.additionalGids,
1104
+ terminal: true
1105
+ });
1106
+ if (options.cols && options.rows)
1107
+ process.consoleSize = { height: options.rows, width: options.cols };
1108
+ const execId = `tty-${randomBytes(8).toString('hex')}`;
1109
+ const directory = path.join(this.stateDir(container.id), 'exec');
1110
+ await mkdir(directory, { recursive: true });
1111
+ const stdin = ProcessFifo.Create(path.join(directory, `${execId}-stdin`), 'write');
1112
+ const stdout = ProcessFifo.Create(path.join(directory, `${execId}-stdout`), 'read');
1113
+ let cleaned = false;
1114
+ const cleanup = async () => {
1115
+ if (cleaned)
1116
+ return;
1117
+ cleaned = true;
1118
+ stdin.close();
1119
+ stdout.close();
1120
+ await this.client.deleteProcess(container.id, execId).catch(() => undefined);
1121
+ await rm(stdin.path, { force: true });
1122
+ await rm(stdout.path, { force: true });
1123
+ };
1124
+ try {
1125
+ await this.client.exec({ containerId: container.id, execId: execId, stdin: stdin.path, stdout: stdout.path, terminal: true, spec: ContainerdClient.Json(PROCESS_TYPE_URL, process) });
1126
+ const waiting = this.client.wait(container.id, execId);
1127
+ await this.client.startTask(container.id, execId);
1128
+ const input = stdin.open();
1129
+ const output = stdout.open();
1130
+ input.on('error', () => undefined);
1131
+ const exited = (async () => {
1132
+ try {
1133
+ return (await waiting).exitStatus;
1134
+ }
1135
+ finally {
1136
+ //Its output read before the FIFOs are closed
1137
+ if (!output.readableEnded)
1138
+ await new Promise((resolve) => {
1139
+ const timer = setTimeout(resolve, 1000);
1140
+ output.once('end', () => {
1141
+ clearTimeout(timer);
1142
+ resolve();
1143
+ });
1144
+ });
1145
+ await cleanup();
1146
+ }
1147
+ })();
1148
+ exited.catch(() => undefined);
1149
+ return {
1150
+ execId: execId,
1151
+ input: input,
1152
+ output: output,
1153
+ resize: async (cols, rows) => this.client.resizePty(container.id, execId, cols, rows),
1154
+ exited: exited,
1155
+ close: async () => {
1156
+ if (cleaned)
1157
+ return;
1158
+ await this.client.kill(container.id, signalNumber('SIGKILL'), { execId: execId }).catch(() => undefined);
1159
+ await exited.catch(() => undefined);
1160
+ }
1161
+ };
1162
+ }
1163
+ catch (error) {
1164
+ await cleanup();
1165
+ throw error;
1166
+ }
1167
+ }
1168
+ /**
1169
+ * Update the resources of a container (its specification, the cgroup of its running task) and its restart policy,
1170
+ * without restarting it (docker update)
1171
+ * @param container
1172
+ * @param update
1173
+ */
1174
+ async updateResources(container, update) {
1175
+ const spec = ContainerdClient.FromJson(container.spec) ?? {};
1176
+ const resources = spec.linux.resources ??= {};
1177
+ //The limits removed: absent from the specification, given to the task as the values of no limit (the update of
1178
+ //runc leaves a 0 or an empty value as it is)
1179
+ const removed = {};
1180
+ if (update.memory !== undefined || update.memorySwap !== undefined || update.oomKillDisable !== undefined) {
1181
+ resources.memory ??= {};
1182
+ if (update.memory === 0) {
1183
+ //No swap limit without a memory limit
1184
+ delete resources.memory.limit;
1185
+ delete resources.memory.swap;
1186
+ removed.memory = removed.swap = true;
1187
+ }
1188
+ else if (update.memory !== undefined) {
1189
+ //A swap limit of twice the memory (the default one) follows the memory
1190
+ if (update.memorySwap === undefined && resources.memory.limit > 0 && resources.memory.swap === resources.memory.limit * 2)
1191
+ resources.memory.swap = update.memory * 2;
1192
+ resources.memory.limit = update.memory;
1193
+ }
1194
+ if (update.memorySwap !== undefined) {
1195
+ if (update.memorySwap > 0 && !(resources.memory.limit > 0))
1196
+ throw Object.assign(new Error('The swap limit of a container (memorySwap) needs its memory limit (memoryLimit)'), { code: 'INVALID_VALUE' });
1197
+ //0: no swap limit; discarded when the kernel does not limit the swap
1198
+ if (!removed.swap && isSwapLimitSupported())
1199
+ resources.memory.swap = update.memorySwap || -1;
1200
+ }
1201
+ if (update.oomKillDisable !== undefined)
1202
+ resources.memory.disableOOMKiller = update.oomKillDisable;
1203
+ if (resources.memory.swap > 0 && resources.memory.swap < resources.memory.limit)
1204
+ throw Object.assign(new Error('The swap limit of a container (memorySwap, its memory and its swap) is less than its memory limit (memoryLimit)'), { code: 'INVALID_VALUE' });
1205
+ }
1206
+ if (update.cpus !== undefined || update.cpuShares !== undefined || update.cpuset !== undefined) {
1207
+ resources.cpu ??= {};
1208
+ if (update.cpus !== undefined) {
1209
+ resources.cpu.quota = update.cpus ? Math.round(update.cpus * 100000) : -1;
1210
+ resources.cpu.period = 100000;
1211
+ }
1212
+ if (update.cpuShares === 0) {
1213
+ delete resources.cpu.shares;
1214
+ removed.shares = true;
1215
+ }
1216
+ else if (update.cpuShares !== undefined) {
1217
+ resources.cpu.shares = update.cpuShares;
1218
+ }
1219
+ if (update.cpuset === '') {
1220
+ delete resources.cpu.cpus;
1221
+ removed.cpus = true;
1222
+ }
1223
+ else if (update.cpuset !== undefined) {
1224
+ resources.cpu.cpus = update.cpuset;
1225
+ }
1226
+ }
1227
+ if (update.pidsLimit !== undefined)
1228
+ resources.pids = { limit: update.pidsLimit || -1 };
1229
+ const labels = {};
1230
+ if (update.restart !== undefined) {
1231
+ const restart = update.restart === 'no' ? '' : update.restart;
1232
+ labels[ContainerLabels.RESTART_POLICY] = restart;
1233
+ labels[ContainerLabels.RESTART_LOG_URI] = restart ? container.labels?.[ContainerLabels.LOG_URI] ?? `file://${this.logFile(container.id)}` : '';
1234
+ const task = await this.client.getTask(container.id);
1235
+ labels[ContainerLabels.RESTART_STATUS] = restart && task?.status === M.ProcessStatus.RUNNING ? 'running' : restart ? 'stopped' : '';
1236
+ }
1237
+ //The labels emptied are deleted
1238
+ await this.client.updateContainer({ id: container.id, spec: ContainerdClient.Json(SPEC_TYPE_URL, spec), labels: Object.fromEntries(Object.entries(labels).filter(([, value]) => !!value)) }, ['spec', ...Object.keys(labels).map((key) => `labels.${key}`)]);
1239
+ const task = await this.client.getTask(container.id);
1240
+ if (!task || task.status === M.ProcessStatus.STOPPED)
1241
+ return;
1242
+ const taskResources = structuredClone(resources);
1243
+ if (removed.memory)
1244
+ taskResources.memory.limit = -1;
1245
+ if (removed.swap)
1246
+ taskResources.memory.swap = -1;
1247
+ //The default weight of the kernel
1248
+ if (removed.shares)
1249
+ taskResources.cpu.shares = 1024;
1250
+ if (removed.cpus)
1251
+ taskResources.cpu.cpus = await onlineCpuSet();
1252
+ await this.client.updateTask(container.id, taskResources);
1253
+ }
1254
+ /**
1255
+ * Commit a container: the changes of its file system as a layer on its image, a new image (its container paused
1256
+ * meanwhile when it runs, by default)
1257
+ * @param container
1258
+ * @param options
1259
+ */
1260
+ async commit(container, options) {
1261
+ const task = await this.client.getTask(container.id);
1262
+ const pause = (options.pause ?? true) && task?.status === M.ProcessStatus.RUNNING;
1263
+ if (pause)
1264
+ await this.client.pause(container.id);
1265
+ try {
1266
+ return await this.images.commit({
1267
+ image: container.image,
1268
+ platform: container.labels?.[ContainerLabels.PLATFORM] ? parsePlatform(container.labels[ContainerLabels.PLATFORM]) : hostPlatform(),
1269
+ snapshotKey: container.snapshotKey || container.id,
1270
+ name: options.name,
1271
+ changes: options.changes,
1272
+ comment: options.comment,
1273
+ author: options.author
1274
+ });
1275
+ }
1276
+ finally {
1277
+ if (pause)
1278
+ await this.client.resume(container.id).catch(() => undefined);
1279
+ }
1280
+ }
1281
+ /**
1282
+ * Archive a path of the file system of a container (tar): the one of its running process (its mounts with it), or
1283
+ * the one of its snapshot when it is not running; its symbolic links resolved in it
1284
+ * @param container
1285
+ * @param file an absolute path in the container
1286
+ * @param destination
1287
+ */
1288
+ async archive(container, file, destination) {
1289
+ const task = await this.client.getTask(container.id);
1290
+ const running = !!task && task.status !== M.ProcessStatus.STOPPED && !!task.pid;
1291
+ const tree = running
1292
+ ? new DirectoryTree(`/proc/${task.pid}/root`)
1293
+ : new LayersTree(layerDirectories(await this.client.snapshotMounts(container.snapshotter || this.options.snapshotter, container.snapshotKey || container.id)));
1294
+ const resolved = await tree.resolve(file);
1295
+ const root = await tree.lstat(resolved);
1296
+ if (!root)
1297
+ throw Object.assign(new Error(`The path ${file} does not exist in the container ${ContainerStore.Name(container)}`), { code: 'PATH_NOT_FOUND' });
1298
+ const archive = tarPack();
1299
+ const output = pipeline(archive, destination);
1300
+ const entry = (header) => new Promise((resolve, reject) => void archive.entry(header, (error) => error ? reject(error) : resolve()));
1301
+ const add = async (name, inner, stats, real) => {
1302
+ const header = { name: name, mode: stats.mode & 0o7777, mtime: stats.mtime, uid: stats.uid, gid: stats.gid };
1303
+ if (stats.isDirectory()) {
1304
+ await entry({ ...header, type: 'directory' });
1305
+ for (const child of await tree.readdir(inner)) {
1306
+ const childInner = path.posix.join(inner, child);
1307
+ const found = await tree.lstat(childInner);
1308
+ if (found)
1309
+ await add(`${name}/${child}`, childInner, found.stats, found.path);
1310
+ }
1311
+ }
1312
+ else if (stats.isSymbolicLink()) {
1313
+ await entry({ ...header, type: 'symlink', linkname: await readlink(real) });
1314
+ }
1315
+ else if (stats.isFile()) {
1316
+ await new Promise((resolve, reject) => {
1317
+ const writer = archive.entry({ ...header, type: 'file', size: stats.size }, (error) => error ? reject(error) : resolve());
1318
+ pipeline(createReadStream(real), writer).catch(reject);
1319
+ });
1320
+ }
1321
+ //The devices, the sockets and the pipes are not archived
1322
+ };
1323
+ try {
1324
+ await add(path.posix.basename(resolved) || '.', resolved, root.stats, root.path);
1325
+ archive.finalize();
1326
+ }
1327
+ catch (error) {
1328
+ archive.destroy(error);
1329
+ }
1330
+ await output;
1331
+ }
1332
+ /**
1333
+ * The name of a container
1334
+ * @param container
1335
+ */
1336
+ static Name(container) {
1337
+ return container.labels?.[ContainerLabels.NAME] ?? container.id;
1338
+ }
1339
+ }