Skip to content

Commit 4eb0527

Browse files
committed
Making the functions generic
1 parent a305990 commit 4eb0527

File tree

2 files changed

+78
-69
lines changed

2 files changed

+78
-69
lines changed

src/spec-node/containerFeatures.ts

Lines changed: 3 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,11 @@ import { LogLevel, makeLog } from '../spec-utils/log';
1111
import { FeaturesConfig, getContainerFeaturesBaseDockerFile, getFeatureInstallWrapperScript, getFeatureLayers, getFeatureMainValue, getFeatureValueObject, generateFeaturesConfig, Feature, generateContainerEnvs } from '../spec-configuration/containerFeaturesConfiguration';
1212
import { readLocalFile } from '../spec-utils/pfs';
1313
import { includeAllConfiguredFeatures } from '../spec-utils/product';
14-
import { createFeaturesTempFolder, DockerResolverParameters, getCacheFolder, getFolderImageName, getEmptyContextFolder, SubstitutedConfig, retry } from './utils';
14+
import { createFeaturesTempFolder, DockerResolverParameters, getCacheFolder, getFolderImageName, getEmptyContextFolder, SubstitutedConfig, ensureDockerfileFrontendAccessible } from './utils';
1515
import { isEarlierVersion, parseVersion, runCommandNoPty } from '../spec-common/commonUtils';
1616
import { getDevcontainerMetadata, getDevcontainerMetadataLabel, getImageBuildInfoFromImage, ImageBuildInfo, ImageMetadataEntry, imageMetadataLabel, MergedDevContainerConfig } from './imageMetadata';
1717
import { supportsBuildContexts } from './dockerfileUtils';
1818
import { ContainerError } from '../spec-common/errors';
19-
import { requestResolveHeaders } from '../spec-utils/httpRequest';
20-
21-
// Constants for DockerHub registry + Dockerfile v1.4 image access check
22-
const DOCKERHUB_AUTH_URL = 'https://auth.docker.io/token?service=registry.docker.io&scope=repository:docker/dockerfile:pull&tag=1.4';
23-
const DOCKERHUB_REGISTRY_URL = 'https://registry-1.docker.io/v2/docker/dockerfile/manifests/1.4';
24-
const DEVCONTAINER_USER_AGENT = 'devcontainer';
25-
const DOCKER_MANIFEST_ACCEPT_HEADER = 'application/vnd.docker.distribution.manifest.v2+json';
26-
const DOCKERFILE_FRONTEND_CHECK_MAX_RETRIES = 5;
27-
const DOCKERFILE_FRONTEND_CHECK_RETRY_INTERVAL_MS = 2000;
2819

2920
// Escapes environment variable keys.
3021
//
@@ -204,7 +195,7 @@ export interface ImageBuildOptions {
204195

205196
async function getImageBuildOptions(params: DockerResolverParameters, config: SubstitutedConfig<DevContainerConfig>, dstFolder: string, baseName: string, imageBuildInfo: ImageBuildInfo): Promise<ImageBuildOptions> {
206197
const syntax = imageBuildInfo.dockerfile?.preamble.directives.syntax;
207-
const dockerHubAccessible = syntax ? await ensureDockerfileFrontendAccessible(params) : false;
198+
const dockerHubAccessible = syntax ? await ensureDockerfileFrontendAccessible(params, 'docker/dockerfile', '1.4') : false;
208199
return {
209200
dstFolder,
210201
dockerfileContent: `
@@ -231,62 +222,6 @@ function getOmitDevcontainerPropertyOverride(resolverParams: { omitConfigRemotEn
231222
return [];
232223
}
233224

234-
async function checkDockerfileFrontendAccessible(params: DockerResolverParameters): Promise<void> {
235-
const { output } = params.common;
236-
237-
const tokenRes = await requestResolveHeaders({
238-
type: 'GET',
239-
url: DOCKERHUB_AUTH_URL,
240-
headers: { 'user-agent': DEVCONTAINER_USER_AGENT }
241-
}, output);
242-
if (!tokenRes || tokenRes.statusCode !== 200) {
243-
throw new Error('Token fetch failed: status ' + (tokenRes?.statusCode ?? 'unknown'));
244-
}
245-
246-
let body: any;
247-
try {
248-
body = JSON.parse(tokenRes.resBody.toString());
249-
} catch (e) {
250-
throw new Error('Token parse failed: ' + (e instanceof Error ? e.message : String(e)));
251-
}
252-
const token: string | undefined = body?.token || body?.access_token;
253-
if (!token) {
254-
throw new Error('Token missing in auth response');
255-
}
256-
257-
const manifestRes = await requestResolveHeaders({
258-
type: 'GET',
259-
url: DOCKERHUB_REGISTRY_URL,
260-
headers: {
261-
'user-agent': DEVCONTAINER_USER_AGENT,
262-
'authorization': `Bearer ${token}`,
263-
'accept': DOCKER_MANIFEST_ACCEPT_HEADER
264-
}
265-
}, output);
266-
if (!manifestRes || manifestRes.statusCode !== 200) {
267-
throw new Error('Manifest fetch failed: status ' + (manifestRes?.statusCode ?? 'unknown'));
268-
}
269-
}
270-
271-
async function ensureDockerfileFrontendAccessible(params: DockerResolverParameters): Promise<boolean> {
272-
const { output } = params.common;
273-
try {
274-
await retry(
275-
async () => { await checkDockerfileFrontendAccessible(params); },
276-
{ maxRetries: DOCKERFILE_FRONTEND_CHECK_MAX_RETRIES, retryIntervalMilliseconds: DOCKERFILE_FRONTEND_CHECK_RETRY_INTERVAL_MS, output }
277-
);
278-
output.write('Dockerfile frontend is accessible in DockerHub registry.', LogLevel.Info);
279-
return true;
280-
} catch (err) {
281-
output.write(
282-
'Dockerfile frontend check failed after retries: ' +
283-
(err instanceof Error ? err.message : String(err)),
284-
LogLevel.Warning
285-
);
286-
return false;
287-
}
288-
}
289-
290225
async function getFeaturesBuildOptions(params: DockerResolverParameters, devContainerConfig: SubstitutedConfig<DevContainerConfig>, featuresConfig: FeaturesConfig, baseName: string, imageBuildInfo: ImageBuildInfo, composeServiceUser: string | undefined): Promise<ImageBuildOptions | undefined> {
291226
const { common } = params;
292227
const { cliHost, output } = common;
@@ -330,7 +265,7 @@ async function getFeaturesBuildOptions(params: DockerResolverParameters, devCont
330265
;
331266
const syntax = imageBuildInfo.dockerfile?.preamble.directives.syntax;
332267
const omitSyntaxDirective = common.omitSyntaxDirective; // Can be removed when https://github.com/moby/buildkit/issues/4556 is fixed
333-
const dockerHubAccessible = !omitSyntaxDirective ? await ensureDockerfileFrontendAccessible(params) : false;
268+
const dockerHubAccessible = !omitSyntaxDirective ? await ensureDockerfileFrontendAccessible(params, 'docker/dockerfile', '1.4') : false;
334269
const dockerfilePrefixContent = `${omitSyntaxDirective ? '' :
335270
useBuildKitBuildContexts && dockerHubAccessible && !(imageBuildInfo.dockerfile && supportsBuildContexts(imageBuildInfo.dockerfile)) ? '# syntax=docker/dockerfile:1.4' :
336271
syntax ? `# syntax=${syntax}` : ''}

src/spec-node/utils.ts

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { ImageMetadataEntry, MergedDevContainerConfig } from './imageMetadata';
2828
import { getImageIndexEntryForPlatform, getManifest, getRef } from '../spec-configuration/containerCollectionsOCI';
2929
import { requestEnsureAuthenticated } from '../spec-configuration/httpOCIRegistry';
3030
import { configFileLabel, findDevContainer, hostFolderLabel } from './singleContainer';
31-
31+
import { requestResolveHeaders } from '../spec-utils/httpRequest';
3232
export { getConfigFilePath, getDockerfilePath, isDockerFileConfig } from '../spec-configuration/configuration';
3333
export { uriToFsPath, parentURI } from '../spec-configuration/configurationCommonUtils';
3434

@@ -37,6 +37,12 @@ export type BindMountConsistency = 'consistent' | 'cached' | 'delegated' | undef
3737

3838
export type GPUAvailability = 'all' | 'detect' | 'none';
3939

40+
// Constants for DockerHub registry + image access check
41+
const DEVCONTAINER_USER_AGENT = 'devcontainer';
42+
const DOCKER_MANIFEST_ACCEPT_HEADER = 'application/vnd.docker.distribution.manifest.v2+json';
43+
const DOCKERFILE_FRONTEND_CHECK_MAX_RETRIES = 5;
44+
const DOCKERFILE_FRONTEND_CHECK_RETRY_INTERVAL_MS = 2000;
45+
4046
// Generic retry function
4147
export async function retry<T>(fn: () => Promise<T>, options: { retryIntervalMilliseconds: number; maxRetries: number; output: Log }): Promise<T> {
4248
const { retryIntervalMilliseconds, maxRetries, output } = options;
@@ -603,3 +609,71 @@ export function runAsyncHandler(handler: () => Promise<void>) {
603609
}
604610
})();
605611
}
612+
613+
// Helper functions to construct DockerHub URLs
614+
function getDockerHubAuthUrl(imageName: string, version: string): string {
615+
return `https://auth.docker.io/token?service=registry.docker.io&scope=repository:${imageName}:pull&tag=${version}`;
616+
}
617+
618+
function getDockerHubRegistryUrl(imageName: string, version: string): string {
619+
return `https://registry-1.docker.io/v2/${imageName}/manifests/${version}`;
620+
}
621+
622+
async function checkDockerfileFrontendAccessible(params: DockerResolverParameters, imageName: string, version: string): Promise<void> {
623+
const { output } = params.common;
624+
625+
const authUrl = getDockerHubAuthUrl(imageName, version);
626+
const registryUrl = getDockerHubRegistryUrl(imageName, version);
627+
628+
const tokenRes = await requestResolveHeaders({
629+
type: 'GET',
630+
url: authUrl,
631+
headers: { 'user-agent': DEVCONTAINER_USER_AGENT }
632+
}, output);
633+
if (!tokenRes || tokenRes.statusCode !== 200) {
634+
throw new Error('Token fetch failed: status ' + (tokenRes?.statusCode ?? 'unknown'));
635+
}
636+
637+
let body: any;
638+
try {
639+
body = JSON.parse(tokenRes.resBody.toString());
640+
} catch (e) {
641+
throw new Error('Token parse failed: ' + (e instanceof Error ? e.message : String(e)));
642+
}
643+
const token: string | undefined = body?.token || body?.access_token;
644+
if (!token) {
645+
throw new Error('Token missing in auth response');
646+
}
647+
648+
const manifestRes = await requestResolveHeaders({
649+
type: 'GET',
650+
url: registryUrl,
651+
headers: {
652+
'user-agent': DEVCONTAINER_USER_AGENT,
653+
'authorization': `Bearer ${token}`,
654+
'accept': DOCKER_MANIFEST_ACCEPT_HEADER
655+
}
656+
}, output);
657+
if (!manifestRes || manifestRes.statusCode !== 200) {
658+
throw new Error('Manifest fetch failed: status ' + (manifestRes?.statusCode ?? 'unknown'));
659+
}
660+
}
661+
662+
export async function ensureDockerfileFrontendAccessible(params: DockerResolverParameters, imageName: string, version: string): Promise<boolean> {
663+
const { output } = params.common;
664+
try {
665+
await retry(
666+
async () => { await checkDockerfileFrontendAccessible(params, imageName, version); },
667+
{ maxRetries: DOCKERFILE_FRONTEND_CHECK_MAX_RETRIES, retryIntervalMilliseconds: DOCKERFILE_FRONTEND_CHECK_RETRY_INTERVAL_MS, output }
668+
);
669+
output.write('Dockerfile frontend is accessible in DockerHub registry.', LogLevel.Info);
670+
return true;
671+
} catch (err) {
672+
output.write(
673+
'Dockerfile frontend check failed after retries: ' +
674+
(err instanceof Error ? err.message : String(err)),
675+
LogLevel.Warning
676+
);
677+
return false;
678+
}
679+
}

0 commit comments

Comments
 (0)