@@ -11,11 +11,12 @@ import { LogLevel, makeLog } from '../spec-utils/log';
1111import { FeaturesConfig , getContainerFeaturesBaseDockerFile , getFeatureInstallWrapperScript , getFeatureLayers , getFeatureMainValue , getFeatureValueObject , generateFeaturesConfig , Feature , generateContainerEnvs } from '../spec-configuration/containerFeaturesConfiguration' ;
1212import { readLocalFile } from '../spec-utils/pfs' ;
1313import { includeAllConfiguredFeatures } from '../spec-utils/product' ;
14- import { createFeaturesTempFolder , DockerResolverParameters , getCacheFolder , getFolderImageName , getEmptyContextFolder , SubstitutedConfig } from './utils' ;
14+ import { createFeaturesTempFolder , DockerResolverParameters , getCacheFolder , getFolderImageName , getEmptyContextFolder , SubstitutedConfig , retry } from './utils' ;
1515import { isEarlierVersion , parseVersion , runCommandNoPty } from '../spec-common/commonUtils' ;
1616import { getDevcontainerMetadata , getDevcontainerMetadataLabel , getImageBuildInfoFromImage , ImageBuildInfo , ImageMetadataEntry , imageMetadataLabel , MergedDevContainerConfig } from './imageMetadata' ;
1717import { supportsBuildContexts } from './dockerfileUtils' ;
1818import { ContainerError } from '../spec-common/errors' ;
19+ import { requestResolveHeaders } from '../spec-utils/httpRequest' ;
1920
2021// Escapes environment variable keys.
2122//
@@ -154,7 +155,7 @@ export async function getExtendImageBuildInfo(params: DockerResolverParameters,
154155 }
155156 } ;
156157 }
157- return { featureBuildInfo : getImageBuildOptions ( params , config , dstFolder , baseName , imageBuildInfo ) } ;
158+ return { featureBuildInfo : await getImageBuildOptions ( params , config , dstFolder , baseName , imageBuildInfo ) } ;
158159 }
159160
160161 // Generates the end configuration.
@@ -193,24 +194,25 @@ export interface ImageBuildOptions {
193194 securityOpts : string [ ] ;
194195}
195196
196- function getImageBuildOptions ( params : DockerResolverParameters , config : SubstitutedConfig < DevContainerConfig > , dstFolder : string , baseName : string , imageBuildInfo : ImageBuildInfo ) : ImageBuildOptions {
197- const syntax = imageBuildInfo . dockerfile ?. preamble . directives . syntax ;
198- return {
199- dstFolder,
200- dockerfileContent : `
197+ async function getImageBuildOptions ( params : DockerResolverParameters , config : SubstitutedConfig < DevContainerConfig > , dstFolder : string , baseName : string , imageBuildInfo : ImageBuildInfo ) : Promise < ImageBuildOptions > {
198+ const syntax = imageBuildInfo . dockerfile ?. preamble . directives . syntax ;
199+ const dockerHubAccessible = syntax ? await ensureDockerfileFrontendAccessible ( params ) : false ;
200+ return {
201+ dstFolder,
202+ dockerfileContent : `
201203FROM $_DEV_CONTAINERS_BASE_IMAGE AS dev_containers_target_stage
202204${ getDevcontainerMetadataLabel ( getDevcontainerMetadata ( imageBuildInfo . metadata , config , { featureSets : [ ] } , [ ] , getOmitDevcontainerPropertyOverride ( params . common ) ) ) }
203205` ,
204- overrideTarget : 'dev_containers_target_stage' ,
205- dockerfilePrefixContent : `${ syntax ? `# syntax=${ syntax } ` : '' }
206- ARG _DEV_CONTAINERS_BASE_IMAGE=placeholder
206+ overrideTarget : 'dev_containers_target_stage' ,
207+ dockerfilePrefixContent : `${ dockerHubAccessible && syntax ? `# syntax=${ syntax } ` : '' }
208+ ARG _DEV_CONTAINERS_BASE_IMAGE=placeholder
207209` ,
208- buildArgs : {
209- _DEV_CONTAINERS_BASE_IMAGE : baseName ,
210- } as Record < string , string > ,
211- buildKitContexts : { } as Record < string , string > ,
212- securityOpts : [ ] ,
213- } ;
210+ buildArgs : {
211+ _DEV_CONTAINERS_BASE_IMAGE : baseName ,
212+ } as Record < string , string > ,
213+ buildKitContexts : { } as Record < string , string > ,
214+ securityOpts : [ ] ,
215+ } ;
214216}
215217
216218function getOmitDevcontainerPropertyOverride ( resolverParams : { omitConfigRemotEnvFromMetadata ?: boolean } ) : ( keyof DevContainerConfig & keyof ImageMetadataEntry ) [ ] {
@@ -221,6 +223,62 @@ function getOmitDevcontainerPropertyOverride(resolverParams: { omitConfigRemotEn
221223 return [ ] ;
222224}
223225
226+ async function checkDockerfileFrontendAccessibleOrThrow ( params : DockerResolverParameters ) : Promise < void > {
227+ const { output } = params . common ;
228+
229+ const tokenRes = await requestResolveHeaders ( {
230+ type : 'GET' ,
231+ url : 'https://auth.docker.io/token?service=registry.docker.io&scope=repository:docker/dockerfile:pull&tag=1.4' ,
232+ headers : { 'user-agent' : 'devcontainer' }
233+ } , output ) ;
234+ if ( ! tokenRes || tokenRes . statusCode !== 200 ) {
235+ throw new Error ( 'Token fetch failed: status ' + ( tokenRes ?. statusCode ?? 'unknown' ) ) ;
236+ }
237+
238+ let body : any ;
239+ try {
240+ body = JSON . parse ( tokenRes . resBody . toString ( ) ) ;
241+ } catch ( e ) {
242+ throw new Error ( 'Token parse failed: ' + ( e instanceof Error ? e . message : String ( e ) ) ) ;
243+ }
244+ const token : string | undefined = body ?. token || body ?. access_token ;
245+ if ( ! token ) {
246+ throw new Error ( 'Token missing in auth response' ) ;
247+ }
248+
249+ const manifestRes = await requestResolveHeaders ( {
250+ type : 'GET' ,
251+ url : 'https://registry-1.docker.io/v2/docker/dockerfile/manifests/1.4' ,
252+ headers : {
253+ 'user-agent' : 'devcontainer' ,
254+ 'authorization' : `Bearer ${ token } ` ,
255+ 'accept' : 'application/vnd.docker.distribution.manifest.v2+json'
256+ }
257+ } , output ) ;
258+ if ( ! manifestRes || manifestRes . statusCode !== 200 ) {
259+ throw new Error ( 'Manifest fetch failed: status ' + ( manifestRes ?. statusCode ?? 'unknown' ) ) ;
260+ }
261+ }
262+
263+ async function ensureDockerfileFrontendAccessible ( params : DockerResolverParameters ) : Promise < boolean > {
264+ const { output } = params . common ;
265+ try {
266+ await retry (
267+ async ( ) => { await checkDockerfileFrontendAccessibleOrThrow ( params ) ; } ,
268+ { maxRetries : 5 , retryIntervalMilliseconds : 2000 , output }
269+ ) ;
270+ output . write ( 'Dockerfile frontend is accessible in DockerHub registry.' , LogLevel . Info ) ;
271+ return true ;
272+ } catch ( err ) {
273+ output . write (
274+ 'Dockerfile frontend check failed after retries: ' +
275+ ( err instanceof Error ? err . message : String ( err ) ) ,
276+ LogLevel . Warning
277+ ) ;
278+ return false ;
279+ }
280+ }
281+
224282async function getFeaturesBuildOptions ( params : DockerResolverParameters , devContainerConfig : SubstitutedConfig < DevContainerConfig > , featuresConfig : FeaturesConfig , baseName : string , imageBuildInfo : ImageBuildInfo , composeServiceUser : string | undefined ) : Promise < ImageBuildOptions | undefined > {
225283 const { common } = params ;
226284 const { cliHost, output } = common ;
@@ -262,11 +320,12 @@ async function getFeaturesBuildOptions(params: DockerResolverParameters, devCont
262320 . replace ( '#{devcontainerMetadata}' , getDevcontainerMetadataLabel ( imageMetadata ) )
263321 . replace ( '#{containerEnvMetadata}' , generateContainerEnvs ( devContainerConfig . config . containerEnv , true ) )
264322 ;
265- const syntax = imageBuildInfo . dockerfile ?. preamble . directives . syntax ;
266- const omitSyntaxDirective = common . omitSyntaxDirective ; // Can be removed when https://github.com/moby/buildkit/issues/4556 is fixed
267- const dockerfilePrefixContent = `${ omitSyntaxDirective ? '' :
268- useBuildKitBuildContexts && ! ( imageBuildInfo . dockerfile && supportsBuildContexts ( imageBuildInfo . dockerfile ) ) ? '# syntax=docker/dockerfile:1.4' :
269- syntax ? `# syntax=${ syntax } ` : '' }
323+ const syntax = imageBuildInfo . dockerfile ?. preamble . directives . syntax ;
324+ const omitSyntaxDirective = common . omitSyntaxDirective ; // Can be removed when https://github.com/moby/buildkit/issues/4556 is fixed
325+ const dockerHubAccessible = ! omitSyntaxDirective ? await ensureDockerfileFrontendAccessible ( params ) : false ;
326+ const dockerfilePrefixContent = `${ omitSyntaxDirective ? '' :
327+ useBuildKitBuildContexts && dockerHubAccessible && ! ( imageBuildInfo . dockerfile && supportsBuildContexts ( imageBuildInfo . dockerfile ) ) ? '# syntax=docker/dockerfile:1.4' :
328+ syntax ? `# syntax=${ syntax } ` : '' }
270329ARG _DEV_CONTAINERS_BASE_IMAGE=placeholder
271330` ;
272331
0 commit comments