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