async function pushImage()

in build/src/push.js [59:184]


async function pushImage(definitionId, repo, release, updateLatest,
    registry, registryPath, stubRegistry, stubRegistryPath, prepOnly, pushImages, replaceImage) {
    const definitionPath = configUtils.getDefinitionPath(definitionId);
    const dotDevContainerPath = path.join(definitionPath, '.devcontainer');
    // Use base.Dockerfile for image build if found, otherwise use Dockerfile
    const dockerFileExists = await asyncUtils.exists(path.join(dotDevContainerPath, 'Dockerfile'));
    const baseDockerFileExists = await asyncUtils.exists(path.join(dotDevContainerPath, 'base.Dockerfile'));
    const dockerFilePath = path.join(dotDevContainerPath, `${baseDockerFileExists ? 'base.' : ''}Dockerfile`);
    
    // Make sure there's a Dockerfile present
    if (!await asyncUtils.exists(dockerFilePath)) {
        throw `Definition ${definitionId} does not exist! Invalid path: ${definitionPath}`;
    }

    // Look for context in devcontainer.json and use it to build the Dockerfile
    console.log('(*) Reading devcontainer.json...');
    const devContainerJsonPath = path.join(dotDevContainerPath, 'devcontainer.json');
    const devContainerJsonRaw = await asyncUtils.readFile(devContainerJsonPath);
    const devContainerJson = jsonc.parse(devContainerJsonRaw);

    // Process variants in reverse order to be sure the first one is tagged as "latest" if appropriate
    const variants = configUtils.getVariants(definitionId) || [null];
    for (let i = variants.length - 1; i > -1; i--) {
        const variant = variants[i];

        // Update common setup script download URL, SHA, parent tag if applicable
        console.log(`(*) Prep Dockerfile for ${definitionId} ${variant ? 'variant "' + variant + '"' : ''}...`);
        const prepResult = await prep.prepDockerFile(dockerFilePath,
            definitionId, repo, release, registry, registryPath, stubRegistry, stubRegistryPath, true, variant);

        if (prepOnly) {
            console.log(`(*) Skipping build and push to registry.`);
        } else {
            if (prepResult.shouldFlattenBaseImage) {
                console.log(`(*) Flattening base image...`);
                await flattenBaseImage(prepResult.baseImageTag, prepResult.flattenedBaseImageTag, pushImages);
            }

            // Build image
            console.log(`(*) Building image...`);
            // Determine tags to use
            const versionTags = configUtils.getTagList(definitionId, release, updateLatest, registry, registryPath, variant);
            console.log(`(*) Tags:${versionTags.reduce((prev, current) => prev += `\n     ${current}`, '')}`);
            const buildSettings = configUtils.getBuildSettings(definitionId);
            let architectures = buildSettings.architectures;
            switch (typeof architectures) {
                case 'string': architectures = [architectures]; break;
                case 'object': if (!Array.isArray(architectures)) { architectures = architectures[variant]; } break;
                case 'undefined': architectures = ['linux/amd64']; break;
            }
            console.log(`(*) Target image architectures: ${architectures.reduce((prev, current) => prev += `\n     ${current}`, '')}`);
            let localArchitecture = process.arch;
            switch(localArchitecture) {
                case 'arm': localArchitecture = 'linux/arm/v7'; break;
                case 'aarch32': localArchitecture = 'linux/arm/v7'; break;
                case 'aarch64': localArchitecture = 'linux/arm64'; break;
                case 'x64': localArchitecture = 'linux/amd64'; break;
                case 'x32': localArchitecture = 'linux/386'; break;
                default: localArchitecture = `linux/${localArchitecture}`; break;
            }
            console.log(`(*) Local architecture: ${localArchitecture}`);
            if (!pushImages) {
                console.log(`(*) Push disabled: Only building local architecture (${localArchitecture}).`);
            }
            if (replaceImage || !await isDefinitionVersionAlreadyPublished(definitionId, release, registry, registryPath, variant)) {
                const context = devContainerJson.build ? devContainerJson.build.context || '.' : devContainerJson.context || '.';
                const workingDir = path.resolve(dotDevContainerPath, context);
                // Add tags to buildx command params
                const buildParams = versionTags.reduce((prev, current) => prev.concat(['-t', current]), []);
                // Note: build.args in devcontainer.json is intentionally ignored so you can vary image contents and defaults as needed
                // Add VARIANT --build-arg if applicable
                if(variant) {
                    buildParams.push('--build-arg', `VARIANT=${variant}`);
                }
                // Generate list of --build-arg values if applicable
                for (let buildArg in buildSettings.buildArgs || {}) {
                    buildParams.push('--build-arg', `${buildArg}=${buildSettings.buildArgs[buildArg]}`);
                }
                // Generate list of variant specific --build-arg values if applicable
                if (buildSettings.variantBuildArgs) {
                    for (let buildArg in buildSettings.variantBuildArgs[variant] || {}) {
                        buildParams.push('--build-arg', `${buildArg}=${buildSettings.variantBuildArgs[variant][buildArg]}`);
                    }
                }
                const spawnOpts = { stdio: 'inherit', cwd: workingDir, shell: true };
                await asyncUtils.spawn('docker', [
                        'buildx',
                        'build',
                        workingDir,
                        '-f', dockerFilePath, 
                        '--label', `version=${prepResult.meta.version}`,
                        `--label`, `${imageLabelPrefix}.id=${prepResult.meta.definitionId}`,
                        '--label', `${imageLabelPrefix}.variant=${prepResult.meta.variant}`,
                        '--label', `${imageLabelPrefix}.release=${prepResult.meta.gitRepositoryRelease}`,
                        '--label', `${imageLabelPrefix}.source=${prepResult.meta.gitRepository}`,
                        '--label', `${imageLabelPrefix}.timestamp='${prepResult.meta.buildTimestamp}'`,
                        '--builder', 'vscode-dev-containers',
                        '--progress', 'plain',
                        '--platform', pushImages ? architectures.reduce((prev, current) => prev + ',' + current, '').substring(1) : localArchitecture,
                        pushImages ? '--push' : '--load',
                        ...buildParams
                    ], spawnOpts);
                if (!pushImages) {
                    console.log(`(*) Skipping push to registry.`);
                }
            } else {
                console.log(`(*) Version already published. Skipping.`);
            }
        }
    }

    // If base.Dockerfile found, update stub/devcontainer.json, otherwise create - just use the default (first) variant if one exists
    if (baseDockerFileExists && dockerFileExists) {
        await prep.updateStub(
            dotDevContainerPath, definitionId, repo, release, baseDockerFileExists, stubRegistry, stubRegistryPath);
        console.log('(*) Updating devcontainer.json...');
        await asyncUtils.writeFile(devContainerJsonPath, devContainerJsonRaw.replace('"base.Dockerfile"', '"Dockerfile"'));
        console.log('(*) Removing base.Dockerfile...');
        await asyncUtils.rimraf(dockerFilePath);
    } else {
        await prep.createStub(
            dotDevContainerPath, definitionId, repo, release, baseDockerFileExists, stubRegistry, stubRegistryPath);
    }

    console.log('(*) Done!\n');
}