@@ -15,7 +15,7 @@ import { readLocalFile } from '../spec-utils/pfs';
15
15
import { includeAllConfiguredFeatures } from '../spec-utils/product' ;
16
16
import { createFeaturesTempFolder , DockerResolverParameters , getCacheFolder , getFolderImageName , getEmptyContextFolder , SubstitutedConfig } from './utils' ;
17
17
import { isEarlierVersion , parseVersion } from '../spec-common/commonUtils' ;
18
- import { getDevcontainerMetadata , getDevcontainerMetadataLabel , getImageBuildInfoFromImage , ImageBuildInfo , MergedDevContainerConfig } from './imageMetadata' ;
18
+ import { getDevcontainerMetadata , getDevcontainerMetadataLabel , getImageBuildInfoFromImage , ImageBuildInfo , ImageMetadataEntry , MergedDevContainerConfig } from './imageMetadata' ;
19
19
import { supportsBuildContexts } from './dockerfileUtils' ;
20
20
21
21
// Escapes environment variable keys.
@@ -34,7 +34,7 @@ export async function extendImage(params: DockerResolverParameters, config: Subs
34
34
const { cliHost, output } = common ;
35
35
36
36
const imageBuildInfo = await getImageBuildInfoFromImage ( params , imageName , config . substitute , common . experimentalImageMetadata ) ;
37
- const extendImageDetails = await getExtendImageBuildInfo ( params , config , imageName , imageBuildInfo , additionalFeatures ) ;
37
+ const extendImageDetails = await getExtendImageBuildInfo ( params , config , imageName , imageBuildInfo , undefined , additionalFeatures ) ;
38
38
if ( ! extendImageDetails || ! extendImageDetails . featureBuildInfo ) {
39
39
// no feature extensions - return
40
40
return {
@@ -94,7 +94,7 @@ export async function extendImage(params: DockerResolverParameters, config: Subs
94
94
} ;
95
95
}
96
96
97
- export async function getExtendImageBuildInfo ( params : DockerResolverParameters , config : SubstitutedConfig < DevContainerConfig > , baseName : string , imageBuildInfo : ImageBuildInfo , additionalFeatures : Record < string , string | boolean | Record < string , string | boolean > > ) {
97
+ export async function getExtendImageBuildInfo ( params : DockerResolverParameters , config : SubstitutedConfig < DevContainerConfig > , baseName : string , imageBuildInfo : ImageBuildInfo , composeServiceUser : string | undefined , additionalFeatures : Record < string , string | boolean | Record < string , string | boolean > > ) {
98
98
99
99
// Creates the folder where the working files will be setup.
100
100
const dstFolder = await createFeaturesTempFolder ( params . common ) ;
@@ -109,7 +109,7 @@ export async function getExtendImageBuildInfo(params: DockerResolverParameters,
109
109
}
110
110
111
111
// Generates the end configuration.
112
- const featureBuildInfo = await getFeaturesBuildOptions ( params , config , featuresConfig , baseName , imageBuildInfo ) ;
112
+ const featureBuildInfo = await getFeaturesBuildOptions ( params , config , featuresConfig , baseName , imageBuildInfo , composeServiceUser ) ;
113
113
if ( ! featureBuildInfo ) {
114
114
return undefined ;
115
115
}
@@ -191,7 +191,7 @@ function getImageBuildOptions(params: DockerResolverParameters, config: Substitu
191
191
dstFolder,
192
192
dockerfileContent : `
193
193
FROM $_DEV_CONTAINERS_BASE_IMAGE AS dev_containers_target_stage
194
- ${ getDevcontainerMetadataLabel ( imageBuildInfo . metadata , config , { featureSets : [ ] } , params . common . experimentalImageMetadata ) }
194
+ ${ getDevcontainerMetadataLabel ( getDevcontainerMetadata ( imageBuildInfo . metadata , config , { featureSets : [ ] } ) , params . common . experimentalImageMetadata ) }
195
195
` ,
196
196
overrideTarget : 'dev_containers_target_stage' ,
197
197
dockerfilePrefixContent : `
@@ -204,7 +204,7 @@ ARG _DEV_CONTAINERS_BASE_IMAGE=placeholder
204
204
} ;
205
205
}
206
206
207
- async function getFeaturesBuildOptions ( params : DockerResolverParameters , devContainerConfig : SubstitutedConfig < DevContainerConfig > , featuresConfig : FeaturesConfig , baseName : string , imageBuildInfo : ImageBuildInfo ) : Promise < ImageBuildOptions | undefined > {
207
+ async function getFeaturesBuildOptions ( params : DockerResolverParameters , devContainerConfig : SubstitutedConfig < DevContainerConfig > , featuresConfig : FeaturesConfig , baseName : string , imageBuildInfo : ImageBuildInfo , composeServiceUser : string | undefined ) : Promise < ImageBuildOptions | undefined > {
208
208
const { common } = params ;
209
209
const { cliHost, output } = common ;
210
210
const { dstFolder } = featuresConfig ;
@@ -239,17 +239,26 @@ async function getFeaturesBuildOptions(params: DockerResolverParameters, devCont
239
239
const useBuildKitBuildContexts = buildKitVersionParsed ? ! isEarlierVersion ( buildKitVersionParsed , minRequiredVersion ) : false ;
240
240
const buildContentImageName = 'dev_container_feature_content_temp' ;
241
241
242
+ const imageMetadata = getDevcontainerMetadata ( imageBuildInfo . metadata , devContainerConfig , featuresConfig ) ;
243
+ const { containerUser, remoteUser } = findContainerUsers ( imageMetadata , composeServiceUser , imageBuildInfo . user ) ;
244
+ const builtinVariables = [
245
+ `_CONTAINER_USER=${ containerUser } ` ,
246
+ `_REMOTE_USER=${ remoteUser } ` ,
247
+ ] ;
248
+ const envPath = cliHost . path . join ( dstFolder , 'devcontainer-features.builtin.env' ) ;
249
+ await cliHost . writeFile ( envPath , Buffer . from ( builtinVariables . join ( '\n' ) + '\n' ) ) ;
250
+
242
251
// When copying via buildkit, the content is accessed via '.' (i.e. in the context root)
243
252
// When copying via temp image, the content is in '/tmp/build-features'
244
253
const contentSourceRootPath = useBuildKitBuildContexts ? '.' : '/tmp/build-features/' ;
245
254
const dockerfile = getContainerFeaturesBaseDockerFile ( )
246
255
. replace ( '#{nonBuildKitFeatureContentFallback}' , useBuildKitBuildContexts ? '' : `FROM ${ buildContentImageName } as dev_containers_feature_content_source` )
247
256
. replace ( '{contentSourceRootPath}' , contentSourceRootPath )
248
257
. replace ( '#{featureBuildStages}' , getFeatureBuildStages ( featuresConfig , buildStageScripts , contentSourceRootPath ) )
249
- . replace ( '#{featureLayer}' , getFeatureLayers ( featuresConfig ) )
258
+ . replace ( '#{featureLayer}' , getFeatureLayers ( featuresConfig , containerUser , remoteUser ) )
250
259
. replace ( '#{containerEnv}' , generateContainerEnvs ( featuresConfig ) )
251
260
. replace ( '#{copyFeatureBuildStages}' , getCopyFeatureBuildStages ( featuresConfig , buildStageScripts ) )
252
- . replace ( '#{devcontainerMetadata}' , getDevcontainerMetadataLabel ( imageBuildInfo . metadata , devContainerConfig , featuresConfig , common . experimentalImageMetadata ) )
261
+ . replace ( '#{devcontainerMetadata}' , getDevcontainerMetadataLabel ( imageMetadata , common . experimentalImageMetadata ) )
253
262
;
254
263
const syntax = imageBuildInfo . dockerfile ?. preamble . directives . syntax ;
255
264
const dockerfilePrefixContent = `${ useBuildKitBuildContexts && ! ( imageBuildInfo . dockerfile && supportsBuildContexts ( imageBuildInfo . dockerfile ) ) ?
@@ -338,6 +347,13 @@ ARG _DEV_CONTAINERS_BASE_IMAGE=placeholder
338
347
} ;
339
348
}
340
349
350
+ export function findContainerUsers ( imageMetadata : SubstitutedConfig < ImageMetadataEntry [ ] > , composeServiceUser : string | undefined , imageUser : string ) {
351
+ const reversed = imageMetadata . config . slice ( ) . reverse ( ) ;
352
+ const containerUser = reversed . find ( entry => entry . containerUser ) ?. containerUser || composeServiceUser || imageUser ;
353
+ const remoteUser = reversed . find ( entry => entry . remoteUser ) ?. remoteUser || containerUser ;
354
+ return { containerUser, remoteUser } ;
355
+ }
356
+
341
357
function getFeatureBuildStages ( featuresConfig : FeaturesConfig , buildStageScripts : Record < string , { hasAcquire : boolean ; hasConfigure : boolean } | undefined > [ ] , contentSourceRootPath : string ) {
342
358
return ( [ ] as string [ ] ) . concat ( ...featuresConfig . featureSets
343
359
. map ( ( featureSet , i ) => featureSet . features
0 commit comments