(buildParams: DockerResolverParameters, configWithRaw: SubstitutedConfig<DevContainerFromDockerfileConfig>, baseImageNames: string[], noCache: boolean, additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>)
| 122 | } |
| 123 | |
| 124 | async function buildAndExtendImage(buildParams: DockerResolverParameters, configWithRaw: SubstitutedConfig<DevContainerFromDockerfileConfig>, baseImageNames: string[], noCache: boolean, additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>) { |
| 125 | const { cliHost, output } = buildParams.common; |
| 126 | const { config } = configWithRaw; |
| 127 | const dockerfileUri = getDockerfilePath(cliHost, config); |
| 128 | const dockerfilePath = await uriToWSLFsPath(dockerfileUri, cliHost); |
| 129 | if (!cliHost.isFile(dockerfilePath)) { |
| 130 | throw new ContainerError({ description: `Dockerfile (${dockerfilePath}) not found.` }); |
| 131 | } |
| 132 | |
| 133 | let dockerfile = (await cliHost.readFile(dockerfilePath)).toString(); |
| 134 | const originalDockerfile = dockerfile; |
| 135 | let baseName = 'dev_container_auto_added_stage_label'; |
| 136 | if (config.build?.target) { |
| 137 | // Explictly set build target for the dev container build features on that |
| 138 | baseName = config.build.target; |
| 139 | } else { |
| 140 | // Use the last stage in the Dockerfile |
| 141 | // Find the last line that starts with "FROM" (possibly preceeded by white-space) |
| 142 | const { lastStageName, modifiedDockerfile } = ensureDockerfileHasFinalStageName(dockerfile, baseName); |
| 143 | baseName = lastStageName; |
| 144 | if (modifiedDockerfile) { |
| 145 | dockerfile = modifiedDockerfile; |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | const imageBuildInfo = await getImageBuildInfoFromDockerfile(buildParams, originalDockerfile, config.build?.args || {}, config.build?.target, configWithRaw.substitute); |
| 150 | const extendImageBuildInfo = await getExtendImageBuildInfo(buildParams, configWithRaw, baseName, imageBuildInfo, undefined, additionalFeatures, false); |
| 151 | |
| 152 | let finalDockerfilePath = dockerfilePath; |
| 153 | const additionalBuildArgs: string[] = []; |
| 154 | if (extendImageBuildInfo?.featureBuildInfo) { |
| 155 | const { featureBuildInfo } = extendImageBuildInfo; |
| 156 | // We add a '# syntax' line at the start, so strip out any existing line |
| 157 | const syntaxMatch = dockerfile.match(/^\s*#\s*syntax\s*=.*[\r\n]/g); |
| 158 | if (syntaxMatch) { |
| 159 | dockerfile = dockerfile.slice(syntaxMatch[0].length); |
| 160 | } |
| 161 | let finalDockerfileContent = `${featureBuildInfo.dockerfilePrefixContent}${dockerfile}\n${featureBuildInfo.dockerfileContent}`; |
| 162 | finalDockerfilePath = cliHost.path.join(featureBuildInfo?.dstFolder, 'Dockerfile-with-features'); |
| 163 | await cliHost.writeFile(finalDockerfilePath, Buffer.from(finalDockerfileContent)); |
| 164 | |
| 165 | // track additional build args to include below |
| 166 | for (const buildContext in featureBuildInfo.buildKitContexts) { |
| 167 | additionalBuildArgs.push('--build-context', `${buildContext}=${featureBuildInfo.buildKitContexts[buildContext]}`); |
| 168 | } |
| 169 | for (const buildArg in featureBuildInfo.buildArgs) { |
| 170 | additionalBuildArgs.push('--build-arg', `${buildArg}=${featureBuildInfo.buildArgs[buildArg]}`); |
| 171 | } |
| 172 | |
| 173 | for (const securityOpt of featureBuildInfo.securityOpts) { |
| 174 | additionalBuildArgs.push('--security-opt', securityOpt); |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | const args: string[] = []; |
| 179 | if (!buildParams.buildKitVersion && |
| 180 | (buildParams.buildxPlatform || buildParams.buildxPush)) { |
| 181 | throw new ContainerError({ description: '--platform or --push require BuildKit enabled.', data: { fileWithError: dockerfilePath } }); |
no test coverage detected