src/dev/build/tasks/os_packages/docker_generator/run.ts (165 lines of code) (raw):

/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the "Elastic License * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side * Public License v 1"; you may not use this file except in compliance with, at * your election, the "Elastic License 2.0", the "GNU Affero General Public * License v3.0 only", or the "Server Side Public License, v 1". */ import { access, link, unlink, chmod } from 'fs'; import { resolve, basename } from 'path'; import { promisify } from 'util'; import { ToolingLog } from '@kbn/tooling-log'; import { kibanaPackageJson } from '@kbn/repo-info'; import { write, copyAll, mkdirp, exec, Config, Build } from '../../../lib'; import * as dockerTemplates from './templates'; import { TemplateContext } from './template_context'; import { bundleDockerFiles } from './bundle_dockerfiles'; const accessAsync = promisify(access); const linkAsync = promisify(link); const unlinkAsync = promisify(unlink); const chmodAsync = promisify(chmod); export async function runDockerGenerator( config: Config, log: ToolingLog, build: Build, flags: { architecture?: string; baseImage: 'none' | 'wolfi' | 'ubi'; context: boolean; image: boolean; ironbank?: boolean; cloud?: boolean; serverless?: boolean; dockerBuildDate?: string; fips?: boolean; } ) { let baseImageName = ''; if (flags.baseImage === 'ubi') baseImageName = 'redhat/ubi9-minimal:latest'; /** * Renovate config contains a regex manager to automatically update both Chainguard references * * If this logic moves to another file or under another name, then the Renovate regex manager * for automatic Chainguard updates will break. */ if (flags.baseImage === 'wolfi') baseImageName = 'docker.elastic.co/wolfi/chainguard-base:latest@sha256:67d82bc56a9c34572abe331c14f5e4b23a284d94a5bc1ea3be64f991ced51892'; let imageFlavor = ''; if (flags.baseImage === 'wolfi' && !flags.serverless && !flags.cloud) imageFlavor += `-wolfi`; if (flags.ironbank) imageFlavor += '-ironbank'; if (flags.cloud) imageFlavor += '-cloud'; if (flags.serverless) imageFlavor += '-serverless'; if (flags.fips) { imageFlavor += '-fips'; baseImageName = 'docker.elastic.co/wolfi/chainguard-base-fips:latest@sha256:6e347a29f0204e8352d1a2e08720d2645f75b4f6e72944cb19e2d7ea4530d467'; } // General docker var config const license = 'Elastic License'; const configuredNamespace = config.getDockerNamespace(); const imageNamespace = configuredNamespace ? configuredNamespace : flags.cloud || flags.serverless ? 'kibana-ci' : 'kibana'; const imageTag = `docker.elastic.co/${imageNamespace}/kibana`; const version = config.getBuildVersion(); const artifactArchitecture = flags.architecture === 'aarch64' ? 'aarch64' : 'x86_64'; let artifactVariant = ''; if (flags.serverless) artifactVariant = '-serverless'; const artifactPrefix = `kibana${artifactVariant}-${version}-linux`; const artifactTarball = `${artifactPrefix}-${artifactArchitecture}.tar.gz`; const beatsArchitecture = flags.architecture === 'aarch64' ? 'arm64' : 'x86_64'; const metricbeatTarball = `metricbeat${ flags.fips ? '-fips' : '' }-${version}-linux-${beatsArchitecture}.tar.gz`; const filebeatTarball = `filebeat${ flags.fips ? '-fips' : '' }-${version}-linux-${beatsArchitecture}.tar.gz`; const artifactsDir = config.resolveFromTarget('.'); const beatsDir = config.resolveFromRepo('.beats'); const dockerBuildDate = flags.dockerBuildDate || new Date().toISOString(); const dockerBuildDir = config.resolveFromRepo('build', 'kibana-docker', `default${imageFlavor}`); const imageArchitecture = flags.architecture === 'aarch64' ? '-aarch64' : ''; const dockerTargetFilename = config.resolveFromTarget( `kibana${imageFlavor}-${version}-docker-image${imageArchitecture}.tar.gz` ); const dependencies = [ resolve(artifactsDir, artifactTarball), ...(flags.cloud ? [resolve(beatsDir, metricbeatTarball), resolve(beatsDir, filebeatTarball)] : []), ]; const dockerPush = config.getDockerPush(); const dockerTag = config.getDockerTag(); const dockerTagQualifier = config.getDockerTagQualfiier(); const dockerCrossCompile = config.getDockerCrossCompile(); const publicArtifactSubdomain = config.isRelease ? 'artifacts' : 'snapshots-no-kpi'; const scope: TemplateContext = { artifactPrefix, artifactTarball, imageFlavor, version, branch: kibanaPackageJson.branch, license, artifactsDir, imageTag, dockerBuildDir, dockerTargetFilename, dockerPush, dockerTag, dockerTagQualifier, dockerCrossCompile, baseImageName, dockerBuildDate, baseImage: flags.baseImage, cloud: flags.cloud, serverless: flags.serverless, metricbeatTarball, filebeatTarball, ironbank: flags.ironbank, architecture: flags.architecture, revision: config.getBuildSha(), publicArtifactSubdomain, fips: flags.fips, }; type HostArchitectureToDocker = Record<string, string>; const hostTarget: HostArchitectureToDocker = { x64: 'x64', arm64: 'aarch64', }; const buildArchitectureSupported = hostTarget[process.arch] === flags.architecture; if (flags.architecture && !buildArchitectureSupported && !dockerCrossCompile) { return; } // Create the docker build target folder await mkdirp(dockerBuildDir); // Write all the needed docker config files // into kibana-docker folder for (const [, dockerTemplate] of Object.entries(dockerTemplates)) { await write(resolve(dockerBuildDir, dockerTemplate.name), dockerTemplate.generator(scope)); } // Copy serverless-only configuration files if (flags.serverless) { await mkdirp(resolve(dockerBuildDir, 'config')); await copyAll(config.resolveFromRepo('config'), resolve(dockerBuildDir, 'config'), { select: ['serverless*.yml'], }); } // Copy all the needed resources into kibana-docker folder // in order to build the docker image accordingly the dockerfile defined // under templates/kibana_yml.template/js await copyAll( config.resolveFromRepo('src/dev/build/tasks/os_packages/docker_generator/resources/base'), dockerBuildDir ); // Copy fips related resources if (flags.fips) { await copyAll( config.resolveFromRepo('src/dev/build/tasks/os_packages/docker_generator/resources/fips'), dockerBuildDir ); } // Build docker image into the target folder // In order to do this we just call the file we // created from the templates/build_docker_sh.template.js // and we just run that bash script await chmodAsync(`${resolve(dockerBuildDir, 'build_docker.sh')}`, '755'); // Only build images on native targets if (flags.image) { // Link dependencies for (const src of dependencies) { const file = basename(src); const dest = resolve(dockerBuildDir, file); try { await accessAsync(src); await unlinkAsync(dest); } catch (e) { if (e && e.code === 'ENOENT' && e.syscall === 'access') { throw new Error(`${src} is needed in order to build the docker image.`); } } await linkAsync(src, dest); } await exec(log, `./build_docker.sh`, [], { cwd: dockerBuildDir, level: 'info', }); } // Pack Dockerfiles and create a target for them if (flags.context) { await bundleDockerFiles(config, log, scope); } }