diff --git a/first-class-pipeline/src/com/freeleaps/devops/ImageBuilder.groovy b/first-class-pipeline/src/com/freeleaps/devops/ImageBuilder.groovy index 6a411af6..0fc25fb5 100644 --- a/first-class-pipeline/src/com/freeleaps/devops/ImageBuilder.groovy +++ b/first-class-pipeline/src/com/freeleaps/devops/ImageBuilder.groovy @@ -9,7 +9,17 @@ class ImageBuilder { def dockerfile def builderType - ImageBuilder(steps, workspace, contextRoot, dockerfile, builderType) { + // customized parameters + def name + def registry + def repository + def architectures + def version + def registryCredentialsId + + def buildxBuilderName + + ImageBuilder(buildId, steps, workspace, contextRoot, dockerfile, builderType) { this.steps = steps this.workspace = workspace this.contextRoot = contextRoot @@ -17,7 +27,58 @@ class ImageBuilder { this.builderType = builderType } - def build(name, repository, registry, architectures, version, registryCredentialsId) { + def setManifestsOfImage(registry, repository, name, version) { + if (registry == null || registry.isEmpty()) { + steps.error("registry is empty") + } + this.registry = registry + + if (repository == null || repository.isEmpty()) { + steps.error("repository is empty") + } + this.repository = repository + + if (name == null || name.isEmpty()) { + steps.error("name is empty") + } + this.name = name + + if (version == null || version.isEmpty()) { + steps.error("version is empty") + } + this.version = version + } + + def setArchitectures(architectures) { + if (architectures == null || architectures.isEmpty()) { + steps.error("architectures is empty") + } + this.architectures = architectures + + if (builderType == ImageBuilderTypes.DOCKER_IN_DOCKER && architectures.size() > 1) { + steps.log.warn("ImageBuilder", "If you want to build multi-arch images and using Docker in Docker (DIND) as builder, system will using buildx to replace build command.") + steps.log.info("ImageBuilder", "Creating buildx builder with name: multiarch-builder-${name}") + steps.sh "docker buildx create --use --name multiarch-builder-${name}" + steps.log.info("ImageBuilder", "Inspecting buildx builder with name: multiarch-builder-${name}") + steps.sh "docker buildx inspect --bootstrap" + steps.log.info("ImageBuilder", "Register clean up hook for buildx builder deletion for builder named: multiarch-builder-${name}") + steps.post { + always { + steps.sh "docker buildx rm multiarch-builder-${name} || true" + } + } + this.buildxBuilderName = "multiarch-builder-${name}" + } + } + + def useCredentials(registryCredentialsId) { + if (registryCredentialsId == null || registryCredentialsId.isEmpty()) { + steps.error("registryCredentialsId is empty") + } + this.registryCredentialsId = registryCredentialsId + } + + def build() { steps.log.info("ImageBuilder", "Building image with ${builderType.builder}") steps.log.info("ImageBuilder", "Workspace sets to: ${workspace}") steps.log.info("ImageBuilder", "Using dockerfile at: ${dockerfile}, context root sets to: ${contextRoot}") @@ -51,11 +112,16 @@ class ImageBuilder { switch(builderType) { case ImageBuilderTypes.DOCKER_IN_DOCKER: steps.dir(workspace) { - architectures.each { architecture -> - def archTag = architecture.split("/")[1] - steps.log.info("ImageBuilder", "Building image ${registry}/${repository}/${name} with architectures: ${architectures}, tag sets to ${version}-${archTag}") - steps.sh "docker build -t ${registry}/${repository}/${name}:${version}-${archTag} --platform ${architecture} -f ${dockerfile} ${contextRoot}" - steps.sh "docker push ${registry}/${repository}/${name}:${version}-${archTag}" + if (buildxBuilderName != null && !buildxBuilderName.isEmpty() && architectures.size() > 1) { + steps.log.info("ImageBuilder", "Building image ${registry}/${repository}/${name} with architectures: ${architectures} using buildx builder: ${buildxBuilderName}, tag sets to ${version}") + steps.sh "docker buildx build --builder ${buildxBuilderName} --platform ${architectures.join(",")} -t ${registry}/${repository}/${name}:${version} -f ${dockerfile} --push ${contextRoot}" + } else { + architectures.each { architecture -> + def archTag = architecture.split("/")[1] + steps.log.info("ImageBuilder", "Building image ${registry}/${repository}/${name} with architectures: ${architectures}, tag sets to ${version}") + steps.sh "docker build -t ${registry}/${repository}/${name}:${version}-${archTag} --platform ${architecture} -f ${dockerfile} ${contextRoot}" + steps.sh "docker push ${registry}/${repository}/${name}:${version}-${archTag}" + } } } break diff --git a/first-class-pipeline/tests/Jenkinsfile b/first-class-pipeline/tests/Jenkinsfile index 2f13e6d5..c594bafe 100644 --- a/first-class-pipeline/tests/Jenkinsfile +++ b/first-class-pipeline/tests/Jenkinsfile @@ -73,7 +73,7 @@ executeFreeleapsPipeline { // imageBuildRoot used to specify the image build context root imageBuildRoot: '.', // imageReleaseArchitectures used to specify the released image architectures - imageReleaseArchitectures: ['linux/amd64', 'linux/arm64'], + imageReleaseArchitectures: ['linux/amd64', 'linux/arm64/v8'], // registryCredentialsId used to specify the registry credential that stored in Jenkins registryCredentialsId: 'gitops-mvp-app-dockerhub-secret', // semanticReleaseEnabled used to specify whether to enable semantic release @@ -120,7 +120,7 @@ executeFreeleapsPipeline { // imageBuildRoot used to specify the image build context root imageBuildRoot: '.', // imageReleaseArchitectures used to specify the released image architectures - imageReleaseArchitectures: ['linux/amd64', 'linux/arm64'], + imageReleaseArchitectures: ['linux/amd64', 'linux/arm64/v8'], // registryCredentialsId used to specify the registry credential that stored in Jenkins registryCredentialsId: 'gitops-mvp-app-dockerhub-secret', // semanticReleaseEnabled used to specify whether to enable semantic release diff --git a/first-class-pipeline/vars/executeFreeleapsPipeline.groovy b/first-class-pipeline/vars/executeFreeleapsPipeline.groovy index ba528725..c8b57674 100644 --- a/first-class-pipeline/vars/executeFreeleapsPipeline.groovy +++ b/first-class-pipeline/vars/executeFreeleapsPipeline.groovy @@ -411,7 +411,12 @@ def generateComponentStages(component, configurations) { log.error("Pipeline", "Unknown image builder for ${component.name}, skipping image building") } - def imageBuilder = new ImageBuilder(this, env.workroot + "/" + component.root + "/", component.imageBuildRoot, component.dockerfilePath, imageBuilderType) + def imageBuilder = new ImageBuilder(this, + env.workroot + "/" + component.root + "/", + component.imageBuildRoot, + component.dockerfilePath, + imageBuilderType + ) log.info("Pipeline", "Retrieve version of image from pervious stage...") if (env.LATEST_VERSION == null || env.LATEST_VERSION.isEmpty()) { @@ -423,7 +428,10 @@ def generateComponentStages(component, configurations) { } def version - imageBuilder.build(component.imageName, component.imageRepository, component.imageRegistry, component.imageReleaseArchitectures, env.LATEST_VERSION, component.registryCredentialsId) + imageBuilder.setManifestsOfImage(component.imageRegistry, component.imageRepository, component.imageName, env.LATEST_VERSION) + imageBuilder.useCredentials(component.registryCredentialsId) + imageBuilder.setArchitectures(component.imageReleaseArchitectures) + imageBuilder.build() } } }