diff --git a/src/spec-node/configContainer.ts b/src/spec-node/configContainer.ts index ee6b93cb..f33623dd 100644 --- a/src/spec-node/configContainer.ts +++ b/src/spec-node/configContainer.ts @@ -23,19 +23,19 @@ import { DockerCLIParameters } from '../spec-shutdown/dockerUtils'; import { createDocuments } from '../spec-configuration/editableFiles'; -export async function resolve(params: DockerResolverParameters, configFile: URI | undefined, overrideConfigFile: URI | undefined, providedIdLabels: string[] | undefined, additionalFeatures: Record>): Promise { +export async function resolve(params: DockerResolverParameters, configFile: URI | undefined, overrideConfigFile: URI | undefined, providedIdLabels: string[] | undefined, additionalFeatures: Record>, skipForwardPorts: boolean): Promise { if (configFile && !/\/\.?devcontainer\.json$/.test(configFile.path)) { throw new Error(`Filename must be devcontainer.json or .devcontainer.json (${uriToFsPath(configFile, params.common.cliHost.platform)}).`); } const parsedAuthority = params.parsedAuthority; if (!parsedAuthority || isDevContainerAuthority(parsedAuthority)) { - return resolveWithLocalFolder(params, parsedAuthority, configFile, overrideConfigFile, providedIdLabels, additionalFeatures); + return resolveWithLocalFolder(params, parsedAuthority, configFile, overrideConfigFile, providedIdLabels, additionalFeatures, skipForwardPorts); } else { throw new Error(`Unexpected authority: ${JSON.stringify(parsedAuthority)}`); } } -async function resolveWithLocalFolder(params: DockerResolverParameters, parsedAuthority: DevContainerAuthority | undefined, configFile: URI | undefined, overrideConfigFile: URI | undefined, providedIdLabels: string[] | undefined, additionalFeatures: Record>): Promise { +async function resolveWithLocalFolder(params: DockerResolverParameters, parsedAuthority: DevContainerAuthority | undefined, configFile: URI | undefined, overrideConfigFile: URI | undefined, providedIdLabels: string[] | undefined, additionalFeatures: Record>, skipForwardPorts: boolean): Promise { const { common, workspaceMountConsistencyDefault } = params; const { cliHost, output } = common; @@ -67,7 +67,7 @@ async function resolveWithLocalFolder(params: DockerResolverParameters, parsedAu let result: ResolverResult; if (isDockerFileConfig(config) || 'image' in config) { - result = await openDockerfileDevContainer(params, configWithRaw as SubstitutedConfig, configs.workspaceConfig, idLabels, additionalFeatures); + result = await openDockerfileDevContainer(params, configWithRaw as SubstitutedConfig, configs.workspaceConfig, idLabels, additionalFeatures, skipForwardPorts); } else if ('dockerComposeFile' in config) { if (!workspace) { throw new ContainerError({ description: `A Dev Container using Docker Compose requires a workspace folder.` }); diff --git a/src/spec-node/devContainers.ts b/src/spec-node/devContainers.ts index 493d0dc2..4046919a 100644 --- a/src/spec-node/devContainers.ts +++ b/src/spec-node/devContainers.ts @@ -58,6 +58,7 @@ export interface ProvisionOptions { buildxCacheTo: string | undefined; additionalFeatures?: Record>; skipFeatureAutoMapping: boolean; + skipForwardPorts: boolean; skipPostAttach: boolean; containerSessionDataFolder?: string; skipPersistingCustomizationsFromFeatures: boolean; @@ -81,7 +82,7 @@ export async function launch(options: ProvisionOptions, providedIdLabels: string const text = 'Resolving Remote'; const start = output.start(text); - const result = await resolve(params, options.configFile, options.overrideConfigFile, providedIdLabels, options.additionalFeatures ?? {}); + const result = await resolve(params, options.configFile, options.overrideConfigFile, providedIdLabels, options.additionalFeatures ?? {}, options.skipForwardPorts); output.stop(text, start); const { dockerContainerId, composeProjectName } = result; return { diff --git a/src/spec-node/devContainersSpecCLI.ts b/src/spec-node/devContainersSpecCLI.ts index 59136695..03f80068 100644 --- a/src/spec-node/devContainersSpecCLI.ts +++ b/src/spec-node/devContainersSpecCLI.ts @@ -130,6 +130,7 @@ function provisionOptions(y: Argv) { 'buildkit': { choices: ['auto' as 'auto', 'never' as 'never'], default: 'auto' as 'auto', description: 'Control whether BuildKit should be used' }, 'additional-features': { type: 'string', description: 'Additional features to apply to the dev container (JSON as per "features" section in devcontainer.json)' }, 'skip-feature-auto-mapping': { type: 'boolean', default: false, hidden: true, description: 'Temporary option for testing.' }, + 'skip-forward-ports': { type: 'boolean', default: false, description: 'Do not publish forwardPorts.' }, 'skip-post-attach': { type: 'boolean', default: false, description: 'Do not run postAttachCommand.' }, 'dotfiles-repository': { type: 'string', description: 'URL of a dotfiles Git repository (e.g., https://github.com/owner/repository.git)' }, 'dotfiles-install-command': { type: 'string', description: 'The command to run after cloning the dotfiles repository. Defaults to run the first file of `install.sh`, `install`, `bootstrap.sh`, `bootstrap`, `setup.sh` and `setup` found in the dotfiles repository`s root folder.' }, @@ -204,6 +205,7 @@ async function provision({ 'buildkit': buildkit, 'additional-features': additionalFeaturesJson, 'skip-feature-auto-mapping': skipFeatureAutoMapping, + 'skip-forward-ports': skipForwardPorts, 'skip-post-attach': skipPostAttach, 'dotfiles-repository': dotfilesRepository, 'dotfiles-install-command': dotfilesInstallCommand, @@ -277,6 +279,7 @@ async function provision({ buildxCacheTo: addCacheTo, additionalFeatures, skipFeatureAutoMapping, + skipForwardPorts, skipPostAttach, containerSessionDataFolder, skipPersistingCustomizationsFromFeatures: false, @@ -680,7 +683,7 @@ async function doBuild({ if (envFile) { composeGlobalArgs.push('--env-file', envFile); } - + const composeConfig = await readDockerComposeConfig(buildParams, composeFiles, envFile); const projectName = await getProjectName(params, workspace, composeFiles, composeConfig); const services = Object.keys(composeConfig.services || {}); diff --git a/src/spec-node/ports.ts b/src/spec-node/ports.ts index 3cc0f247..a92e5241 100644 --- a/src/spec-node/ports.ts +++ b/src/spec-node/ports.ts @@ -6,6 +6,7 @@ function normalizePorts(ports: number | string | (number | string)[] | undefined return ports.map((port) => typeof port === 'number' ? `127.0.0.1:${port}:${port}`: port); } -export function getStaticPorts(config: DevContainerFromDockerfileConfig | DevContainerFromImageConfig): string[] { - return normalizePorts(config.forwardPorts).concat(normalizePorts(config.appPort)); +export function getStaticPorts(config: DevContainerFromDockerfileConfig | DevContainerFromImageConfig, skipForwardPorts: boolean): string[] { + const forwardPorts = skipForwardPorts ? undefined : config.forwardPorts; + return normalizePorts(forwardPorts).concat(normalizePorts(config.appPort)); } diff --git a/src/spec-node/singleContainer.ts b/src/spec-node/singleContainer.ts index 0cfdf214..7596df31 100644 --- a/src/spec-node/singleContainer.ts +++ b/src/spec-node/singleContainer.ts @@ -18,7 +18,7 @@ import { getStaticPorts } from './ports'; export const hostFolderLabel = 'devcontainer.local_folder'; // used to label containers created from a workspace/folder export const configFileLabel = 'devcontainer.config_file'; -export async function openDockerfileDevContainer(params: DockerResolverParameters, configWithRaw: SubstitutedConfig, workspaceConfig: WorkspaceConfiguration, idLabels: string[], additionalFeatures: Record>): Promise { +export async function openDockerfileDevContainer(params: DockerResolverParameters, configWithRaw: SubstitutedConfig, workspaceConfig: WorkspaceConfiguration, idLabels: string[], additionalFeatures: Record>, skipForwardPorts: boolean): Promise { const { common } = params; const { config } = configWithRaw; // let collapsedFeaturesConfig: () => Promise; @@ -52,7 +52,7 @@ export async function openDockerfileDevContainer(params: DockerResolverParameter // collapsedFeaturesConfig = async () => res.collapsedFeaturesConfig; try { - await spawnDevContainer(params, config, mergedConfig, updatedImageName, idLabels, workspaceConfig.workspaceMount, res.imageDetails, containerUser, res.labels || {}); + await spawnDevContainer(params, config, mergedConfig, updatedImageName, idLabels, workspaceConfig.workspaceMount, res.imageDetails, containerUser, res.labels || {}, skipForwardPorts); } finally { // In 'finally' because 'docker run' can fail after creating the container. // Trying to get it here, so we can offer 'Rebuild Container' as an action later. @@ -345,11 +345,11 @@ export async function extraRunArgs(common: ResolverParameters, params: DockerRes return extraArguments; } -export async function spawnDevContainer(params: DockerResolverParameters, config: DevContainerFromDockerfileConfig | DevContainerFromImageConfig, mergedConfig: MergedDevContainerConfig, imageName: string, labels: string[], workspaceMount: string | undefined, imageDetails: (() => Promise) | undefined, containerUser: string | undefined, extraLabels: Record) { +export async function spawnDevContainer(params: DockerResolverParameters, config: DevContainerFromDockerfileConfig | DevContainerFromImageConfig, mergedConfig: MergedDevContainerConfig, imageName: string, labels: string[], workspaceMount: string | undefined, imageDetails: (() => Promise) | undefined, containerUser: string | undefined, extraLabels: Record, skipForwardPorts: boolean) { const { common } = params; common.progress(ResolverProgress.StartingContainer); - const exposedPorts = getStaticPorts(config); + const exposedPorts = getStaticPorts(config, skipForwardPorts); const exposed = exposedPorts.flatMap((port) => ['-p', port]); const cwdMount = workspaceMount ? ['--mount', workspaceMount] : [];