-
Notifications
You must be signed in to change notification settings - Fork 4.3k
feat(core): allow mounting existing or volume copy volumes #32832
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4298466
9387c9d
eeeaaef
c36ec02
93c9728
06dda4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,8 @@ | ||
| import { spawnSync } from 'child_process'; | ||
| import * as crypto from 'crypto'; | ||
| import { isAbsolute, join } from 'path'; | ||
| import { DockerCacheOption } from './assets'; | ||
| import { FileSystem } from './fs'; | ||
| import { dockerExec } from './private/asset-staging'; | ||
| import { dockerExec, DockerVolumeHelper } from './private/bundling'; | ||
| import { quiet, reset } from './private/jsii-deprecated'; | ||
|
|
||
| /** | ||
|
|
@@ -61,7 +60,7 @@ export interface BundlingOptions { | |
| * | ||
| * @default - no additional volumes are mounted | ||
| */ | ||
| readonly volumes?: DockerVolume[]; | ||
| readonly volumes?: (DockerVolume | VolumeCopyDockerVolume | ExistingDockerVolume)[]; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As you already surmised, this is a problem because it provides a poor user experience for static languages, where this translates into an generic Before we consider alternatives, I have some questions.
|
||
|
|
||
| /** | ||
| * Where to mount the specified volumes from | ||
|
|
@@ -255,7 +254,6 @@ export class BundlingDockerImage { | |
| * Runs a Docker image | ||
| */ | ||
| public run(options: DockerRunOptions = {}) { | ||
| const volumes = options.volumes || []; | ||
| const environment = options.environment || {}; | ||
| const entrypoint = options.entrypoint?.[0] || null; | ||
| const command = [ | ||
|
|
@@ -267,36 +265,39 @@ export class BundlingDockerImage { | |
| : [], | ||
| ]; | ||
|
|
||
| const dockerArgs: string[] = [ | ||
| 'run', '--rm', | ||
| ...options.securityOpt | ||
| ? ['--security-opt', options.securityOpt] | ||
| : [], | ||
| ...options.network | ||
| ? ['--network', options.network] | ||
| : [], | ||
| ...options.platform | ||
| ? ['--platform', options.platform] | ||
| : [], | ||
| ...options.user | ||
| ? ['-u', options.user] | ||
| : [], | ||
| ...options.volumesFrom | ||
| ? flatten(options.volumesFrom.map(v => ['--volumes-from', v])) | ||
| : [], | ||
| ...flatten(volumes.map(v => ['-v', `${v.hostPath}:${v.containerPath}:${isSeLinux() ? 'z,' : ''}${v.consistency ?? DockerVolumeConsistency.DELEGATED}`])), | ||
| ...flatten(Object.entries(environment).map(([k, v]) => ['--env', `${k}=${v}`])), | ||
| ...options.workingDirectory | ||
| ? ['-w', options.workingDirectory] | ||
| : [], | ||
| ...entrypoint | ||
| ? ['--entrypoint', entrypoint] | ||
| : [], | ||
| this.image, | ||
| ...command, | ||
| ]; | ||
| const volumeHelper = new DockerVolumeHelper(options); | ||
|
|
||
| dockerExec(dockerArgs); | ||
| try { | ||
| const dockerArgs: string[] = [ | ||
| 'run', '--rm', | ||
| ...options.securityOpt | ||
| ? ['--security-opt', options.securityOpt] | ||
| : [], | ||
| ...options.network | ||
| ? ['--network', options.network] | ||
| : [], | ||
| ...options.platform | ||
| ? ['--platform', options.platform] | ||
| : [], | ||
| ...options.user | ||
| ? ['-u', options.user] | ||
| : [], | ||
| ...volumeHelper.volumeCommands, | ||
| ...flatten(Object.entries(environment).map(([k, v]) => ['--env', `${k}=${v}`])), | ||
| ...options.workingDirectory | ||
| ? ['-w', options.workingDirectory] | ||
| : [], | ||
| ...entrypoint | ||
| ? ['--entrypoint', entrypoint] | ||
| : [], | ||
| this.image, | ||
| ...command, | ||
| ]; | ||
|
|
||
| dockerExec(dockerArgs); | ||
| } finally { | ||
| volumeHelper.cleanup(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -461,19 +462,62 @@ export class DockerImage extends BundlingDockerImage { | |
| } | ||
|
|
||
| /** | ||
| * A Docker volume | ||
| * The access mechanism used to make this volume available to the bundling container | ||
| */ | ||
| export interface DockerVolume { | ||
| export enum DockerVolumeType { | ||
| /** | ||
| * The path to the file or directory on the host machine | ||
| * Creates temporary volumes and containers to copy files from the host to the bundling container and back. | ||
| * This is slower, but works also in more complex situations with remote or shared docker sockets. | ||
| */ | ||
| readonly hostPath: string; | ||
| VOLUME_COPY = 'VOLUME_COPY', | ||
|
|
||
| /** | ||
| * The source and output folders will be mounted as bind mount from the host system | ||
| * This is faster and simpler, but less portable than `VOLUME_COPY`. | ||
| */ | ||
| BIND_MOUNT = 'BIND_MOUNT', | ||
|
|
||
| /** | ||
| * The volume already exists and will not be created or destroyed by this class | ||
| */ | ||
| EXISTING = 'EXISTING', | ||
| } | ||
|
|
||
| /** | ||
| * Common properties for all Docker volume types. | ||
| */ | ||
| export interface DockerVolumeBase { | ||
| /** | ||
| * The path where the file or directory is mounted in the container | ||
| * The path inside the container where the volume is mounted. | ||
| * This property is required for all volume types. | ||
| */ | ||
| readonly containerPath: string; | ||
|
|
||
| /** | ||
| * `--volume` options to use when mounting this volume to the docker container. | ||
| * | ||
| * @default - 'z' option is used for selinux bind mounts | ||
| */ | ||
| readonly opts?: string[]; | ||
| } | ||
|
|
||
| /** | ||
| * Configuration for a `BIND_MOUNT` type Docker volume. | ||
| */ | ||
| export interface DockerVolume extends DockerVolumeBase { | ||
| /** | ||
| * The type of the Docker volume. | ||
| * | ||
| * @default DockerVolumeType.BIND_MOUNT | ||
| */ | ||
| readonly dockerVolumeType?: DockerVolumeType.BIND_MOUNT; | ||
|
|
||
| /** | ||
| * The path on the host machine to be mounted as a bind mount. | ||
| * This property is required for `BIND_MOUNT` volumes. | ||
| */ | ||
| readonly hostPath: string; | ||
|
|
||
| /** | ||
| * Mount consistency. Only applicable for macOS | ||
| * | ||
|
|
@@ -483,6 +527,48 @@ export interface DockerVolume { | |
| readonly consistency?: DockerVolumeConsistency; | ||
| } | ||
|
|
||
| /** | ||
| * Configuration for a `VOLUME_COPY` type Docker volume. | ||
| */ | ||
| export interface VolumeCopyDockerVolume extends DockerVolumeBase { | ||
| /** | ||
| * The type of the Docker volume. | ||
| * | ||
| * @default DockerVolumeType.BIND_MOUNT | ||
| */ | ||
| readonly dockerVolumeType: DockerVolumeType.VOLUME_COPY; | ||
|
|
||
| /** | ||
| * The path on the host machine to be used as the input for the volume. | ||
| * @default - Does not copy from the host machine | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then why would we define a VolumeCopy volume if we aren't copying anything? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is optional to support the cases where we only want to copy things out of the container, such as the asset staging output. I can make the doc more explicit and throw an exception if both this and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another alternative is to have a required |
||
| */ | ||
| readonly hostInputPath?: string; | ||
|
|
||
| /** | ||
| * The path on the host machine where the output from the volume will be written. | ||
| * @default - Does not copy to the host machine | ||
| */ | ||
| readonly hostOutputPath?: string; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this capability is really related to what this PR is trying to address. There is also no test for it. In general I don't think we need this, it poses a slight shift in how we think about bundling. Currently, the bundling mechanism takes N inputs and produces 1 output - thats clear and easy to reason about. Allowing for M outputs makes it more complex and I don't see a reason for it. If you have a clear use case in mind - please elaborate on it, preferably in a different issue/PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use case for this (and the original incentive for this PR) is to allow mounting dependency cache folders (e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On further thought it's also required for the existing asset staging behaviour - even for 1 output scenarios, if the user opts for volume copy then asset staging needs the hostOutputPath functionality for the asset output. |
||
| } | ||
|
|
||
| /** | ||
| * Configuration for an `EXISTING` type Docker volume. | ||
| */ | ||
| export interface ExistingDockerVolume extends DockerVolumeBase { | ||
| /** | ||
| * The type of the Docker volume. | ||
| * | ||
| * @default DockerVolumeType.BIND_MOUNT | ||
| */ | ||
| readonly dockerVolumeType: DockerVolumeType.EXISTING; | ||
|
|
||
| /** | ||
| * The name of the existing volume. | ||
| * This property is required for `EXISTING` volumes. | ||
| */ | ||
| readonly volumeName: string; | ||
| } | ||
|
|
||
| /** | ||
| * Supported Docker volume consistency types. Only valid on macOS due to the way file storage works on Mac | ||
| */ | ||
|
|
@@ -524,7 +610,7 @@ export interface DockerRunOptions { | |
| * | ||
| * @default - no volumes are mounted | ||
| */ | ||
| readonly volumes?: DockerVolume[]; | ||
| readonly volumes?: (DockerVolume | VolumeCopyDockerVolume | ExistingDockerVolume)[]; | ||
|
|
||
| /** | ||
| * Where to mount the specified volumes from | ||
|
|
@@ -640,28 +726,3 @@ export interface DockerBuildOptions { | |
| function flatten(x: string[][]) { | ||
| return Array.prototype.concat([], ...x); | ||
| } | ||
|
|
||
| function isSeLinux(): boolean { | ||
| if (process.platform != 'linux') { | ||
| return false; | ||
| } | ||
| const prog = 'selinuxenabled'; | ||
| const proc = spawnSync(prog, [], { | ||
| stdio: [ // show selinux status output | ||
| 'pipe', // get value of stdio | ||
| process.stderr, // redirect stdout to stderr | ||
| 'inherit', // inherit stderr | ||
| ], | ||
| }); | ||
| if (proc.error) { | ||
| // selinuxenabled not a valid command, therefore not enabled | ||
| return false; | ||
| } | ||
| if (proc.status == 0) { | ||
| // selinux enabled | ||
| return true; | ||
| } else { | ||
| // selinux not enabled | ||
| return false; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I prefer these individual helper classes over the proposed
DockerVolumeHelper- even at the expense of some duplication. Since volume commands differ so much between the two options, a common helper class makes it harder to reason through.