Skip to content

Commit ebdb5ae

Browse files
authored
Features contribute lifecycle hooks (#390)
* refactor Feature interface * Feat: Add in inline command lifecycle hook contribution from Features * markerfile to exercise order of execution * add in two helper scripts with the same name in two different Features * test with ${featureRoot} * make Feature artifacts executable by all users * test for advanced lifehook scenario * add * typecheck * improve test * refactor variable names from 'postCreate' -> 'lifecycleHook' * refactor variable names from 'postCreate' -> 'lifecycleHook' * update test for running in parallel * update test * Display the 'origin' of the lifecycle command in the output log during 'up' Logs like 'Running the postCreateCommand from devcontainer.json' are updated to include the origin of the command e.g: 'Running the postCreateCommand from Feature 'common-utils'. This patch adds in a mapping object that is passed along and used to map a command to an origin. This change is a bit less efficient, with the added benefit of not needing to modify the existing merging logic. * Change copy path for Features on-container from /tmp-build-features to /opt/build-features This is to support resuming a container in Codespaces with Feature-contributed lifecycle hooks. Codespaces bind-mounts a different volume on top of /tmp, so the container's /tmp directory is not persisted. * run lifecycle commands in Feature install order * stop passing config object where unused * Rename LifecycleCommandOriginMap -> LifecycleHooksInstallMap * add test to assert installsAfter is respected * rm -rf test dir * assert contents in metadata label * unused test stub * exercise dev container test cmd * increase test timeouts * '' -> '' * pass contentSourceRootPath as a variable instead of string subst * remove dead code referencing "hasAcquires" * cannot change line 256 * persist final Features copy to /usr/share/devcontainer/features * update existing test * feature dir need not be writable by anyone but its owner * add test to assert resume will trigger lifecycle hooks * Test: "${featureRootFolder} substituted lifecycle hooks trigger on resume * missing semicolon * preserve featureRootFolder in metadata and replace right before usage * rename metadata entry to featureRootFolder for clarity and only emit in featureRaw * merged config has substituted featureRootFolder values * update tests to remove ${featureRootFolder} * remove ${featureRootFolder} variable substiution * move Features container build folder back to /tmp
1 parent 6ee5243 commit ebdb5ae

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+895
-183
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ build-tmp
1111
.DS_Store
1212
.env
1313
output
14+
*.testMarker
15+
src/test/container-features/configs/temp_lifecycle-hooks-alternative-order

src/spec-common/injectHeadless.ts

+70-51
Large diffs are not rendered by default.

src/spec-common/variableSubstitution.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,4 @@ function devcontainerIdForLabels(idLabels: Record<string, string>): string {
168168
.toString(32)
169169
.padStart(52, '0');
170170
return uniqueId;
171-
}
171+
}

src/spec-configuration/containerFeaturesConfiguration.ts

+62-34
Original file line numberDiff line numberDiff line change
@@ -23,34 +23,55 @@ export const V1_DEVCONTAINER_FEATURES_FILE_NAME = 'devcontainer-features.json';
2323
// v2
2424
export const DEVCONTAINER_FEATURE_FILE_NAME = 'devcontainer-feature.json';
2525

26-
export interface Feature {
26+
export type Feature = SchemaFeatureBaseProperties & SchemaFeatureLifecycleHooks & DeprecatedSchemaFeatureProperties & InternalFeatureProperties;
27+
28+
export const FEATURES_CONTAINER_TEMP_DEST_FOLDER = '/tmp/dev-container-features';
29+
30+
export interface SchemaFeatureLifecycleHooks {
31+
onCreateCommand?: string | string[];
32+
updateContentCommand?: string | string[];
33+
postCreateCommand?: string | string[];
34+
postStartCommand?: string | string[];
35+
postAttachCommand?: string | string[];
36+
}
37+
38+
// Properties who are members of the schema
39+
export interface SchemaFeatureBaseProperties {
2740
id: string;
2841
version?: string;
2942
name?: string;
3043
description?: string;
31-
cachePath?: string;
32-
internalVersion?: string; // set programmatically
33-
consecutiveId?: string;
3444
documentationURL?: string;
3545
licenseURL?: string;
3646
options?: Record<string, FeatureOption>;
37-
buildArg?: string; // old properties for temporary compatibility
3847
containerEnv?: Record<string, string>;
3948
mounts?: Mount[];
4049
init?: boolean;
4150
privileged?: boolean;
4251
capAdd?: string[];
4352
securityOpt?: string[];
4453
entrypoint?: string;
45-
include?: string[];
46-
exclude?: string[];
47-
value: boolean | string | Record<string, boolean | string | undefined>; // set programmatically
48-
included: boolean; // set programmatically
4954
customizations?: VSCodeCustomizations;
5055
installsAfter?: string[];
5156
deprecated?: boolean;
5257
legacyIds?: string[];
53-
currentId?: string; // set programmatically
58+
}
59+
60+
// Properties that are set programmatically for book-keeping purposes
61+
export interface InternalFeatureProperties {
62+
cachePath?: string;
63+
internalVersion?: string;
64+
consecutiveId?: string;
65+
value: boolean | string | Record<string, boolean | string | undefined>;
66+
currentId?: string;
67+
included: boolean;
68+
}
69+
70+
// Old or deprecated properties maintained for backwards compatibility
71+
export interface DeprecatedSchemaFeatureProperties {
72+
buildArg?: string;
73+
include?: string[];
74+
exclude?: string[];
5475
}
5576

5677
export type FeatureOption = {
@@ -218,27 +239,25 @@ export function getSourceInfoString(srcInfo: SourceInformation): string {
218239
}
219240

220241
// TODO: Move to node layer.
221-
export function getContainerFeaturesBaseDockerFile() {
242+
export function getContainerFeaturesBaseDockerFile(contentSourceRootPath: string) {
222243
return `
223-
#{featureBuildStages}
224244
225245
#{nonBuildKitFeatureContentFallback}
226246
227247
FROM $_DEV_CONTAINERS_BASE_IMAGE AS dev_containers_feature_content_normalize
228248
USER root
229-
COPY --from=dev_containers_feature_content_source {contentSourceRootPath}/devcontainer-features.builtin.env /tmp/build-features/
230-
RUN chmod -R 0700 /tmp/build-features
249+
COPY --from=dev_containers_feature_content_source ${path.posix.join(contentSourceRootPath, 'devcontainer-features.builtin.env')} /tmp/build-features/
250+
RUN chmod -R 0755 /tmp/build-features/
231251
232252
FROM $_DEV_CONTAINERS_BASE_IMAGE AS dev_containers_target_stage
233253
234254
USER root
235255
236-
COPY --from=dev_containers_feature_content_normalize /tmp/build-features /tmp/build-features
256+
RUN mkdir -p ${FEATURES_CONTAINER_TEMP_DEST_FOLDER}
257+
COPY --from=dev_containers_feature_content_normalize /tmp/build-features/ ${FEATURES_CONTAINER_TEMP_DEST_FOLDER}
237258
238259
#{featureLayer}
239260
240-
#{copyFeatureBuildStages}
241-
242261
#{containerEnv}
243262
244263
ARG _DEV_CONTAINERS_IMAGE_USER=root
@@ -312,33 +331,36 @@ function escapeQuotesForShell(input: string) {
312331
return input.replace(new RegExp(`'`, 'g'), `'\\''`);
313332
}
314333

315-
export function getFeatureLayers(featuresConfig: FeaturesConfig, containerUser: string, remoteUser: string, useBuildKitBuildContexts = false, contentSourceRootPath = '/tmp/build-features/') {
334+
export function getFeatureLayers(featuresConfig: FeaturesConfig, containerUser: string, remoteUser: string, useBuildKitBuildContexts = false, contentSourceRootPath = '/tmp/build-features') {
335+
336+
const builtinsEnvFile = `${path.posix.join(FEATURES_CONTAINER_TEMP_DEST_FOLDER, 'devcontainer-features.builtin.env')}`;
316337
let result = `RUN \\
317-
echo "_CONTAINER_USER_HOME=$(getent passwd ${containerUser} | cut -d: -f6)" >> /tmp/build-features/devcontainer-features.builtin.env && \\
318-
echo "_REMOTE_USER_HOME=$(getent passwd ${remoteUser} | cut -d: -f6)" >> /tmp/build-features/devcontainer-features.builtin.env
338+
echo "_CONTAINER_USER_HOME=$(getent passwd ${containerUser} | cut -d: -f6)" >> ${builtinsEnvFile} && \\
339+
echo "_REMOTE_USER_HOME=$(getent passwd ${remoteUser} | cut -d: -f6)" >> ${builtinsEnvFile}
319340
320341
`;
321342

322343
// Features version 1
323344
const folders = (featuresConfig.featureSets || []).filter(y => y.internalVersion !== '2').map(x => x.features[0].consecutiveId);
324345
folders.forEach(folder => {
325346
const source = path.posix.join(contentSourceRootPath, folder!);
347+
const dest = path.posix.join(FEATURES_CONTAINER_TEMP_DEST_FOLDER, folder!);
326348
if (!useBuildKitBuildContexts) {
327-
result += `COPY --chown=root:root --from=dev_containers_feature_content_source ${source} /tmp/build-features/${folder}
328-
RUN chmod -R 0700 /tmp/build-features/${folder} \\
329-
&& cd /tmp/build-features/${folder} \\
349+
result += `COPY --chown=root:root --from=dev_containers_feature_content_source ${source} ${dest}
350+
RUN chmod -R 0755 ${dest} \\
351+
&& cd ${dest} \\
330352
&& chmod +x ./install.sh \\
331353
&& ./install.sh
332354
333355
`;
334356
} else {
335357
result += `RUN --mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${folder} \\
336-
cp -ar /tmp/build-features-src/${folder} /tmp/build-features/ \\
337-
&& chmod -R 0700 /tmp/build-features/${folder} \\
338-
&& cd /tmp/build-features/${folder} \\
358+
cp -ar /tmp/build-features-src/${folder} ${FEATURES_CONTAINER_TEMP_DEST_FOLDER} \\
359+
&& chmod -R 0755 ${dest} \\
360+
&& cd ${dest} \\
339361
&& chmod +x ./install.sh \\
340362
&& ./install.sh \\
341-
&& rm -rf /tmp/build-features/${folder}
363+
&& rm -rf ${dest}
342364
343365
`;
344366
}
@@ -348,24 +370,25 @@ RUN chmod -R 0700 /tmp/build-features/${folder} \\
348370
featureSet.features.forEach(feature => {
349371
result += generateContainerEnvs(feature);
350372
const source = path.posix.join(contentSourceRootPath, feature.consecutiveId!);
373+
const dest = path.posix.join(FEATURES_CONTAINER_TEMP_DEST_FOLDER, feature.consecutiveId!);
351374
if (!useBuildKitBuildContexts) {
352375
result += `
353-
COPY --chown=root:root --from=dev_containers_feature_content_source ${source} /tmp/build-features/${feature.consecutiveId}
354-
RUN chmod -R 0700 /tmp/build-features/${feature.consecutiveId} \\
355-
&& cd /tmp/build-features/${feature.consecutiveId} \\
376+
COPY --chown=root:root --from=dev_containers_feature_content_source ${source} ${dest}
377+
RUN chmod -R 0755 ${dest} \\
378+
&& cd ${dest} \\
356379
&& chmod +x ./devcontainer-features-install.sh \\
357380
&& ./devcontainer-features-install.sh
358381
359382
`;
360383
} else {
361384
result += `
362385
RUN --mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${feature.consecutiveId} \\
363-
cp -ar /tmp/build-features-src/${feature.consecutiveId} /tmp/build-features/ \\
364-
&& chmod -R 0700 /tmp/build-features/${feature.consecutiveId} \\
365-
&& cd /tmp/build-features/${feature.consecutiveId} \\
386+
cp -ar /tmp/build-features-src/${feature.consecutiveId} ${FEATURES_CONTAINER_TEMP_DEST_FOLDER} \\
387+
&& chmod -R 0755 ${dest} \\
388+
&& cd ${dest} \\
366389
&& chmod +x ./devcontainer-features-install.sh \\
367390
&& ./devcontainer-features-install.sh \\
368-
&& rm -rf /tmp/build-features/${feature.consecutiveId}
391+
&& rm -rf ${dest}
369392
370393
`;
371394
}
@@ -938,6 +961,11 @@ async function fetchFeatures(params: { extensionPath: string; cwd: string; outpu
938961
feature.cachePath = featCachePath;
939962
feature.consecutiveId = consecutiveId;
940963

964+
if (!feature.consecutiveId || !feature.id || !featureSet?.sourceInformation || !featureSet.sourceInformation.userFeatureId) {
965+
const err = 'Internal Features error. Missing required attribute(s).';
966+
throw new Error(err);
967+
}
968+
941969
const featureDebugId = `${feature.consecutiveId}_${sourceInfoType}`;
942970
output.write(`* Fetching feature: ${featureDebugId}`);
943971

src/spec-node/configContainer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ async function resolveWithLocalFolder(params: DockerResolverParameters, parsedAu
5656
const configWithRaw = addSubstitution(configs.config, config => beforeContainerSubstitute(envListToObj(idLabels), config));
5757
const { config } = configWithRaw;
5858

59-
await runUserCommand({ ...params, common: { ...common, output: common.postCreate.output } }, config.initializeCommand, common.postCreate.onDidInput);
59+
await runUserCommand({ ...params, common: { ...common, output: common.lifecycleHook.output } }, config.initializeCommand, common.lifecycleHook.onDidInput);
6060

6161
let result: ResolverResult;
6262
if (isDockerFileConfig(config) || 'image' in config) {

src/spec-node/containerFeatures.ts

+1-29
Original file line numberDiff line numberDiff line change
@@ -285,13 +285,10 @@ async function getFeaturesBuildOptions(params: DockerResolverParameters, devCont
285285
// When copying via buildkit, the content is accessed via '.' (i.e. in the context root)
286286
// When copying via temp image, the content is in '/tmp/build-features'
287287
const contentSourceRootPath = useBuildKitBuildContexts ? '.' : '/tmp/build-features/';
288-
const dockerfile = getContainerFeaturesBaseDockerFile()
288+
const dockerfile = getContainerFeaturesBaseDockerFile(contentSourceRootPath)
289289
.replace('#{nonBuildKitFeatureContentFallback}', useBuildKitBuildContexts ? '' : `FROM ${buildContentImageName} as dev_containers_feature_content_source`)
290-
.replace('{contentSourceRootPath}', contentSourceRootPath)
291-
.replace('#{featureBuildStages}', getFeatureBuildStages(featuresConfig, buildStageScripts, contentSourceRootPath))
292290
.replace('#{featureLayer}', getFeatureLayers(featuresConfig, containerUser, remoteUser, useBuildKitBuildContexts, contentSourceRootPath))
293291
.replace('#{containerEnv}', generateContainerEnvs(featuresConfig))
294-
.replace('#{copyFeatureBuildStages}', getCopyFeatureBuildStages(featuresConfig, buildStageScripts))
295292
.replace('#{devcontainerMetadata}', getDevcontainerMetadataLabel(imageMetadata, common.experimentalImageMetadata))
296293
.replace('#{containerEnvMetadata}', getContainerEnvMetadata(devContainerConfig.config.containerEnv))
297294
;
@@ -389,31 +386,6 @@ export function findContainerUsers(imageMetadata: SubstitutedConfig<ImageMetadat
389386
return { containerUser, remoteUser };
390387
}
391388

392-
function getFeatureBuildStages(featuresConfig: FeaturesConfig, buildStageScripts: Record<string, { hasAcquire: boolean; hasConfigure: boolean } | undefined>[], contentSourceRootPath: string) {
393-
return ([] as string[]).concat(...featuresConfig.featureSets
394-
.map((featureSet, i) => featureSet.features
395-
.filter(f => (includeAllConfiguredFeatures || f.included) && f.value && buildStageScripts[i][f.id]?.hasAcquire)
396-
.map(f => `FROM mcr.microsoft.com/vscode/devcontainers/base:0-focal as ${getSourceInfoString(featureSet.sourceInformation)}_${f.id}
397-
COPY --from=dev_containers_feature_content_normalize ${path.posix.join(contentSourceRootPath, getSourceInfoString(featureSet.sourceInformation), 'features', f.id)} ${path.posix.join('/tmp/build-features', getSourceInfoString(featureSet.sourceInformation), 'features', f.id)}
398-
COPY --from=dev_containers_feature_content_normalize ${path.posix.join(contentSourceRootPath, getSourceInfoString(featureSet.sourceInformation), 'common')} ${path.posix.join('/tmp/build-features', getSourceInfoString(featureSet.sourceInformation), 'common')}
399-
RUN cd ${path.posix.join('/tmp/build-features', getSourceInfoString(featureSet.sourceInformation), 'features', f.id)} && set -a && . ./devcontainer-features.env && set +a && ./bin/acquire`
400-
)
401-
)
402-
).join('\n\n');
403-
}
404-
405-
function getCopyFeatureBuildStages(featuresConfig: FeaturesConfig, buildStageScripts: Record<string, { hasAcquire: boolean; hasConfigure: boolean } | undefined>[]) {
406-
return ([] as string[]).concat(...featuresConfig.featureSets
407-
.map((featureSet, i) => featureSet.features
408-
.filter(f => (includeAllConfiguredFeatures || f.included) && f.value && buildStageScripts[i][f.id]?.hasAcquire)
409-
.map(f => {
410-
const featurePath = path.posix.join('/usr/local/devcontainer-features', getSourceInfoString(featureSet.sourceInformation), f.id);
411-
return `COPY --from=${getSourceInfoString(featureSet.sourceInformation)}_${f.id} ${featurePath} ${featurePath}${buildStageScripts[i][f.id]?.hasConfigure ? `
412-
RUN cd ${path.posix.join('/tmp/build-features', getSourceInfoString(featureSet.sourceInformation), 'features', f.id)} && set -a && . ./devcontainer-features.env && set +a && ./bin/configure` : ''}`;
413-
})
414-
)
415-
).join('\n\n');
416-
}
417389

418390
function getFeatureEnvVariables(f: Feature) {
419391
const values = getFeatureValueObject(f);

src/spec-node/devContainers.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as crypto from 'crypto';
88
import * as os from 'os';
99

1010
import { DockerResolverParameters, DevContainerAuthority, UpdateRemoteUserUIDDefault, BindMountConsistency, getCacheFolder } from './utils';
11-
import { createNullPostCreate, finishBackgroundTasks, ResolverParameters, UserEnvProbe } from '../spec-common/injectHeadless';
11+
import { createNullLifecycleHook, finishBackgroundTasks, ResolverParameters, UserEnvProbe } from '../spec-common/injectHeadless';
1212
import { getCLIHost, loadNativeModule } from '../spec-common/commonUtils';
1313
import { resolve } from './configContainer';
1414
import { URI } from 'vscode-uri';
@@ -122,7 +122,7 @@ export async function createDockerParams(options: ProvisionOptions, disposables:
122122
output,
123123
allowSystemConfigChange: true,
124124
defaultUserEnvProbe: options.defaultUserEnvProbe,
125-
postCreate: createNullPostCreate(options.postCreateEnabled, options.skipNonBlocking, output),
125+
lifecycleHook: createNullLifecycleHook(options.postCreateEnabled, options.skipNonBlocking, output),
126126
getLogLevel: () => options.logLevel,
127127
onDidChangeLogLevel: () => ({ dispose() { } }),
128128
loadNativeModule,

src/spec-node/devContainersSpecCLI.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import { SubstitutedConfig, createContainerProperties, createFeaturesTempFolder,
1313
import { URI } from 'vscode-uri';
1414
import { ContainerError } from '../spec-common/errors';
1515
import { Log, LogLevel, makeLog, mapLogLevel } from '../spec-utils/log';
16-
import { probeRemoteEnv, runPostCreateCommands, runRemoteCommand, UserEnvProbe, setupInContainer } from '../spec-common/injectHeadless';
17-
import { bailOut, buildNamedImageAndExtend } from './singleContainer';
16+
import { probeRemoteEnv, runLifecycleHooks, runRemoteCommand, UserEnvProbe, setupInContainer } from '../spec-common/injectHeadless';
1817
import { extendImage } from './containerFeatures';
1918
import { DockerCLIParameters, dockerPtyCLI, inspectContainer } from '../spec-shutdown/dockerUtils';
2019
import { buildAndExtendDockerCompose, dockerComposeCLIConfig, getDefaultImageName, getProjectName, readDockerComposeConfig, readVersionPrefix } from './dockerCompose';
@@ -31,10 +30,11 @@ import { featuresPublishHandler, featuresPublishOptions } from './featuresCLI/pu
3130
import { featureInfoTagsHandler, featuresInfoTagsOptions } from './featuresCLI/infoTags';
3231
import { beforeContainerSubstitute, containerSubstitute, substitute } from '../spec-common/variableSubstitution';
3332
import { getPackageConfig, PackageConfiguration } from '../spec-utils/product';
34-
import { getDevcontainerMetadata, getImageBuildInfo, getImageMetadataFromContainer, ImageMetadataEntry, mergeConfiguration, MergedDevContainerConfig } from './imageMetadata';
33+
import { getDevcontainerMetadata, getImageBuildInfo, getImageMetadataFromContainer, ImageMetadataEntry, lifecycleCommandOriginMapFromMetadata, mergeConfiguration, MergedDevContainerConfig } from './imageMetadata';
3534
import { templatesPublishHandler, templatesPublishOptions } from './templatesCLI/publish';
3635
import { templateApplyHandler, templateApplyOptions } from './templatesCLI/apply';
3736
import { featuresInfoManifestHandler, featuresInfoManifestOptions } from './featuresCLI/infoManifest';
37+
import { bailOut, buildNamedImageAndExtend } from './singleContainer';
3838

3939
const defaultDefaultUserEnvProbe: UserEnvProbe = 'loginInteractiveShell';
4040

@@ -423,7 +423,7 @@ async function doSetUp({
423423
const imageMetadata = getImageMetadataFromContainer(container, config, undefined, undefined, true, output).config;
424424
const mergedConfig = mergeConfiguration(config.config, imageMetadata);
425425
const containerProperties = await createContainerProperties(params, container.Id, configs?.workspaceConfig.workspaceFolder, mergedConfig.remoteUser);
426-
await setupInContainer(common, containerProperties, mergedConfig);
426+
await setupInContainer(common, containerProperties, mergedConfig, lifecycleCommandOriginMapFromMetadata(imageMetadata));
427427
return {
428428
outcome: 'success' as 'success',
429429
dispose,
@@ -834,7 +834,7 @@ async function doRunUserCommands({
834834
const containerProperties = await createContainerProperties(params, container.Id, configs?.workspaceConfig.workspaceFolder, mergedConfig.remoteUser);
835835
const updatedConfig = containerSubstitute(cliHost.platform, config.config.configFilePath, containerProperties.env, mergedConfig);
836836
const remoteEnv = probeRemoteEnv(common, containerProperties, updatedConfig);
837-
const result = await runPostCreateCommands(common, containerProperties, updatedConfig, remoteEnv, stopForPersonalization);
837+
const result = await runLifecycleHooks(common, lifecycleCommandOriginMapFromMetadata(imageMetadata), containerProperties, updatedConfig, remoteEnv, stopForPersonalization);
838838
return {
839839
outcome: 'success' as 'success',
840840
result,

src/spec-node/dockerCompose.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { Log, LogLevel, makeLog, terminalEscapeSequences } from '../spec-utils/l
1717
import { getExtendImageBuildInfo, updateRemoteUserUID } from './containerFeatures';
1818
import { Mount, parseMount } from '../spec-configuration/containerFeaturesConfiguration';
1919
import path from 'path';
20-
import { getDevcontainerMetadata, getImageBuildInfoFromDockerfile, getImageBuildInfoFromImage, getImageMetadataFromContainer, ImageBuildInfo, mergeConfiguration, MergedDevContainerConfig } from './imageMetadata';
20+
import { getDevcontainerMetadata, getImageBuildInfoFromDockerfile, getImageBuildInfoFromImage, getImageMetadataFromContainer, ImageBuildInfo, lifecycleCommandOriginMapFromMetadata, mergeConfiguration, MergedDevContainerConfig } from './imageMetadata';
2121
import { ensureDockerfileHasFinalStageName } from './dockerfileUtils';
2222

2323
const projectLabel = 'com.docker.compose.project';
@@ -68,13 +68,13 @@ async function _openDockerComposeDevContainer(params: DockerResolverParameters,
6868
// collapsedFeaturesConfig = collapseFeaturesConfig(featuresConfig);
6969
}
7070

71-
const configs = getImageMetadataFromContainer(container, configWithRaw, undefined, idLabels, common.experimentalImageMetadata, common.output).config;
72-
const mergedConfig = mergeConfiguration(configWithRaw.config, configs);
71+
const imageMetadata = getImageMetadataFromContainer(container, configWithRaw, undefined, idLabels, common.experimentalImageMetadata, common.output).config;
72+
const mergedConfig = mergeConfiguration(configWithRaw.config, imageMetadata);
7373
containerProperties = await createContainerProperties(params, container.Id, remoteWorkspaceFolder, mergedConfig.remoteUser);
7474

7575
const {
7676
remoteEnv: extensionHostEnv,
77-
} = await setupInContainer(common, containerProperties, mergedConfig);
77+
} = await setupInContainer(common, containerProperties, mergedConfig, lifecycleCommandOriginMapFromMetadata(imageMetadata));
7878

7979
return {
8080
params: common,

0 commit comments

Comments
 (0)