diff --git a/src/spec-configuration/containerFeaturesConfiguration.ts b/src/spec-configuration/containerFeaturesConfiguration.ts index f85267be7..a80c700e8 100644 --- a/src/spec-configuration/containerFeaturesConfiguration.ts +++ b/src/spec-configuration/containerFeaturesConfiguration.ts @@ -289,7 +289,7 @@ function escapeQuotesForShell(input: string) { return input.replace(new RegExp(`'`, 'g'), `'\\''`); } -export function getFeatureLayers(featuresConfig: FeaturesConfig, containerUser: string, remoteUser: string, useBuildKitBuildContexts = false, contentSourceRootPath = '/tmp/build-features') { +export function getFeatureLayers(featuresConfig: FeaturesConfig, containerUser: string, remoteUser: string, isBuildah = false, useBuildKitBuildContexts = false, contentSourceRootPath = '/tmp/build-features') { const builtinsEnvFile = `${path.posix.join(FEATURES_CONTAINER_TEMP_DEST_FOLDER, 'devcontainer-features.builtin.env')}`; let result = `RUN \\ @@ -312,7 +312,7 @@ RUN chmod -R 0755 ${dest} \\ `; } else { - result += `RUN --mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${folder} \\ + result += `RUN --mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${folder}${isBuildah ? ',z' : ''} \\ cp -ar /tmp/build-features-src/${folder} ${FEATURES_CONTAINER_TEMP_DEST_FOLDER} \\ && chmod -R 0755 ${dest} \\ && cd ${dest} \\ @@ -340,7 +340,7 @@ RUN chmod -R 0755 ${dest} \\ `; } else { result += ` -RUN --mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${feature.consecutiveId} \\ +RUN --mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${feature.consecutiveId}${isBuildah ? ',z' : ''} \\ cp -ar /tmp/build-features-src/${feature.consecutiveId} ${FEATURES_CONTAINER_TEMP_DEST_FOLDER} \\ && chmod -R 0755 ${dest} \\ && cd ${dest} \\ diff --git a/src/spec-node/containerFeatures.ts b/src/spec-node/containerFeatures.ts index 5b0a2159c..3bbc0d952 100644 --- a/src/spec-node/containerFeatures.ts +++ b/src/spec-node/containerFeatures.ts @@ -216,10 +216,11 @@ async function getFeaturesBuildOptions(params: DockerResolverParameters, devCont // For non-Buildkit, we build a temporary image to hold the container-features content in a way // that is accessible from the docker build for non-BuiltKit builds // TODO generate an image name that is specific to this dev container? - const buildKitVersionParsed = params.buildKitVersion ? parseVersion(params.buildKitVersion) : null; + const buildKitVersionParsed = params.buildKitVersion?.versionMatch ? parseVersion(params.buildKitVersion.versionMatch) : undefined; const minRequiredVersion = [0, 8, 0]; const useBuildKitBuildContexts = buildKitVersionParsed ? !isEarlierVersion(buildKitVersionParsed, minRequiredVersion) : false; const buildContentImageName = 'dev_container_feature_content_temp'; + const isBuildah = !!params.buildKitVersion?.versionString.toLowerCase().includes('buildah'); const omitPropertyOverride = params.common.skipPersistingCustomizationsFromFeatures ? ['customizations'] : []; const imageMetadata = getDevcontainerMetadata(imageBuildInfo.metadata, devContainerConfig, featuresConfig, omitPropertyOverride, getOmitDevcontainerPropertyOverride(params.common)); @@ -236,7 +237,7 @@ async function getFeaturesBuildOptions(params: DockerResolverParameters, devCont const contentSourceRootPath = useBuildKitBuildContexts ? '.' : '/tmp/build-features/'; const dockerfile = getContainerFeaturesBaseDockerFile(contentSourceRootPath) .replace('#{nonBuildKitFeatureContentFallback}', useBuildKitBuildContexts ? '' : `FROM ${buildContentImageName} as dev_containers_feature_content_source`) - .replace('#{featureLayer}', getFeatureLayers(featuresConfig, containerUser, remoteUser, useBuildKitBuildContexts, contentSourceRootPath)) + .replace('#{featureLayer}', getFeatureLayers(featuresConfig, containerUser, remoteUser, isBuildah, useBuildKitBuildContexts, contentSourceRootPath)) .replace('#{containerEnv}', generateContainerEnvsV1(featuresConfig)) .replace('#{devcontainerMetadata}', getDevcontainerMetadataLabel(imageMetadata)) .replace('#{containerEnvMetadata}', generateContainerEnvs(devContainerConfig.config.containerEnv, true)) diff --git a/src/spec-node/devContainers.ts b/src/spec-node/devContainers.ts index e7a7c6538..8cd8f9b6d 100644 --- a/src/spec-node/devContainers.ts +++ b/src/spec-node/devContainers.ts @@ -161,7 +161,7 @@ export async function createDockerParams(options: ProvisionOptions, disposables: env: cliHost.env, output: common.output, }, dockerPath, dockerComposePath); - const buildKitVersion = options.useBuildKit === 'never' ? null : (await dockerBuildKitVersion({ + const buildKitVersion = options.useBuildKit === 'never' ? undefined : (await dockerBuildKitVersion({ cliHost, dockerCLI: dockerPath, dockerComposeCLI, diff --git a/src/spec-node/dockerCompose.ts b/src/spec-node/dockerCompose.ts index 398a4d58c..f695a0cf3 100644 --- a/src/spec-node/dockerCompose.ts +++ b/src/spec-node/dockerCompose.ts @@ -182,7 +182,7 @@ export async function buildAndExtendDockerCompose(configWithRaw: SubstitutedConf } // determine whether we need to extend with features - const noBuildKitParams = { ...params, buildKitVersion: null }; // skip BuildKit -> can't set additional build contexts with compose + const noBuildKitParams = { ...params, buildKitVersion: undefined }; // skip BuildKit -> can't set additional build contexts with compose const extendImageBuildInfo = await getExtendImageBuildInfo(noBuildKitParams, configWithRaw, baseName, imageBuildInfo, composeService.user, additionalFeatures, canAddLabelsToContainer); let overrideImageName: string | undefined; diff --git a/src/spec-node/utils.ts b/src/spec-node/utils.ts index bb202d900..cf25c95b0 100644 --- a/src/spec-node/utils.ts +++ b/src/spec-node/utils.ts @@ -109,7 +109,7 @@ export interface DockerResolverParameters { additionalMounts: Mount[]; updateRemoteUserUIDDefault: UpdateRemoteUserUIDDefault; additionalCacheFroms: string[]; - buildKitVersion: string | null; + buildKitVersion: { versionString: string; versionMatch?: string } | undefined; isTTY: boolean; experimentalLockfile?: boolean; experimentalFrozenLockfile?: boolean; diff --git a/src/spec-shutdown/dockerUtils.ts b/src/spec-shutdown/dockerUtils.ts index 2dbb02703..7dc2a289f 100644 --- a/src/spec-shutdown/dockerUtils.ts +++ b/src/spec-shutdown/dockerUtils.ts @@ -198,20 +198,21 @@ export async function getEvents(params: DockerCLIParameters | DockerResolverPara return p; } -export async function dockerBuildKitVersion(params: DockerCLIParameters | PartialExecParameters | DockerResolverParameters): Promise { +export async function dockerBuildKitVersion(params: DockerCLIParameters | PartialExecParameters | DockerResolverParameters): Promise<{ versionString: string; versionMatch?: string } | undefined> { try { const execParams = { ...toExecParameters(params), print: true, }; const result = await dockerCLI(execParams, 'buildx', 'version'); - const versionMatch = result.stdout.toString().match(/(?[0-9]+)\.(?[0-9]+)\.(?[0-9]+)/); + const versionString = result.stdout.toString(); + const versionMatch = versionString.match(/(?[0-9]+)\.(?[0-9]+)\.(?[0-9]+)/); if (!versionMatch) { - return null; + return { versionString }; } - return versionMatch[0]; + return { versionString, versionMatch: versionMatch[0] }; } catch { - return null; + return undefined; } }