Skip to content

Commit

Permalink
feat(buildDockerAndPublishImage): allow to specify a target when usin…
Browse files Browse the repository at this point in the history
…g docker bake (#822)

* feat(buildDockerAndPublishImage): allow to specify a target when using docker bake

* allow cst to test a specific tag of an image

* allow cst to test a specific tag of an image

* Update vars/buildDockerAndPublishImage.txt

Co-authored-by: Damien Duportal <[email protected]>

---------

Co-authored-by: Damien Duportal <[email protected]>
  • Loading branch information
lemeurherve and dduportal authored Jan 11, 2024
1 parent 3b389aa commit 61da11f
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 16 deletions.
1 change: 1 addition & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ Name of the docker image to build
* registryNamespace: empty = autodiscover based on the current controller, but can override the smart default of jenkinsciinfra/ or jenkins4eval/
* unstash: Allow to unstash files if not empty
* dockerBakeFile: Allow to build from a bake file instead
* dockerBakeTarget: Allow to specify a docker bake target other than 'default'

==== Example
[source, groovy]
Expand Down
33 changes: 24 additions & 9 deletions resources/io/jenkins/infra/docker/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ else
IMAGE_DIR := $(abspath $(PWD)/$(IMAGE_DIR))
endif

current_arch := $(shell uname -m)
export ARCH ?= $(shell case $(current_arch) in (x86_64) echo "amd64" ;; (i386) echo "386";; (aarch64|arm64) echo "arm64" ;; (armv6*) echo "arm/v6";; (armv7*) echo "arm/v7";; (s390*|riscv*|ppc64le) echo $(current_arch);; (*) echo "UNKNOWN-CPU";; esac)

IMAGE_NAME ?= helloworld
IMAGE_DEPLOY_NAME ?= "$(IMAGE_NAME)"
BUILD_TARGETPLATFORM ?= linux/amd64
Expand All @@ -26,6 +29,7 @@ endif
HADOLINT_REPORT ?= "$(IMAGE_DIR)"/hadolint.json
TEST_HARNESS ?= "$(IMAGE_DIR)"/cst.yml
DOCKER_BAKE_FILE ?= "$(IMAGE_DIR)"/docker-bake.hcl
DOCKER_BAKE_TARGET ?= default

## Image metadatas
GIT_COMMIT_REV ?= $(shell git log -n 1 --pretty=format:'%h')
Expand Down Expand Up @@ -75,9 +79,9 @@ build: ## Build the Docker Image $(IMAGE_NAME) from $(IMAGE_DOCKERFILE)
@echo "== Build succeeded"

bake-build: ## Build the Docker Image(s) with dockerbake file
@echo "== Building from DockerBake file $(DOCKER_BAKE_FILE)"
@docker buildx bake -f "$(DOCKER_BAKE_FILE)" --print
@docker buildx bake -f "$(DOCKER_BAKE_FILE)"
@echo "== Building ${DOCKER_BAKE_TARGET} target from DockerBake file $(DOCKER_BAKE_FILE)"
@make show
@docker buildx bake -f "$(DOCKER_BAKE_FILE)" "$(DOCKER_BAKE_TARGET)"
@echo "== Build succeeded"


Expand All @@ -91,11 +95,22 @@ test: ## Execute the test harness on the Docker Image
container-structure-test test --driver=docker --image="$(IMAGE_NAME)" --config="$(call FixPath,$(TEST_HARNESS))"
@echo "== Test succeeded"

bake-test: ## Execute the test harness on the Docker Image with load
show: ## Show the output of docker buildx bake --print
@docker buildx bake -f "$(DOCKER_BAKE_FILE)" "$(DOCKER_BAKE_TARGET)" --print

list: ## List the first tag of each target including the same platform than the current architecture, with "/" replaced by "#"
@set -x; make --silent show | jq -r '.target | to_entries[] | select(.value.platforms[] | contains("linux/$(ARCH)")) | .value.tags[0] | gsub("/"; "#")'

bake-test: ## Execute the test harness on the Docker image(s) with load
@echo "== Load $(IMAGE_NAME) within docker engine from docker bake buildx engine"
@docker buildx bake -f "$(call FixPath,$(DOCKER_BAKE_FILE))" --set "*.platform=linux/$(shell dpkg --print-architecture)" --load
@echo "== Test $(IMAGE_NAME) with $(call FixPath,$(TEST_HARNESS)) from $(IMAGE_NAME) with container-structure-test:"
container-structure-test test --driver=docker --image="$(IMAGE_NAME)" --config="$(call FixPath,$(TEST_HARNESS))"
@make show
@docker buildx bake -f "$(call FixPath,$(DOCKER_BAKE_FILE))" "$(DOCKER_BAKE_TARGET)" --set "*.platform=linux/$(ARCH)" --load
@make --silent list | while read image; do make --silent "bake-test-$${image}"; done
@echo "== All tests succeeded"

bake-test-%: ## Execute the test harness on the tag of an image
@echo "== Test $(subst #,/,$*) with $(call FixPath,$(TEST_HARNESS)) from $(IMAGE_NAME) with container-structure-test:"
container-structure-test test --driver=docker --image="$(subst #,/,$*)" --config="$(call FixPath,$(TEST_HARNESS))"
@echo "== Test succeeded"

## This steps expects that you are logged to the Docker registry to push image into
Expand All @@ -106,7 +121,7 @@ deploy: ## Tag and push the built image as specified by $(IMAGE_DEPLOY).
@echo "== Deploy succeeded"

bake-deploy: ## Tag and push the built image as specified by docker bake file
@echo "== Deploying with docker bake file"
@docker buildx bake -f "$(call FixPath,$(DOCKER_BAKE_FILE))" --push
@echo "== Deploying $(DOCKER_BAKE_TARGET) target with docker bake file"
@docker buildx bake -f "$(call FixPath,$(DOCKER_BAKE_FILE))" "$(DOCKER_BAKE_TARGET)" --push

.PHONY: all clean lint build test deploy
39 changes: 38 additions & 1 deletion test/groovy/BuildDockerAndPublishImageStepTests.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class BuildDockerAndPublishImageStepTests extends BaseTest {
static final String defaultNextVersionCommand = 'jx-release-version'
static final String defaultOrigin = 'https://github.com/org/repository.git'
static final String defaultReleaseId = '12345'
static final String defaultDockerBakeFile = 'jenkinsinfrabakefile.hcl'

def infraConfigMock
def dateMock
Expand Down Expand Up @@ -644,6 +645,42 @@ class BuildDockerAndPublishImageStepTests extends BaseTest {
assertTrue(assertMethodCallContainsPattern('withEnv', 'BAKE_TARGETPLATFORMS=linux/amd64'))
assertTrue(assertMethodCallContainsPattern('withEnv', 'IMAGE_DOCKERFILE=Dockerfile'))
assertTrue(assertMethodCallContainsPattern('withEnv', 'DOCKER_BAKE_FILE=bake.yml'))
assertTrue(assertMethodCallContainsPattern('withEnv', 'DOCKER_BAKE_TARGET=default'))
// // And all mocked/stubbed methods have to be called
verifyMocks()
}

@Test
void itBuildsAndDeploysImageWithCustomDockerBakeTargetOnPrincipalBranch() throws Exception {
def script = loadScript(scriptName)
mockPrincipalBranch()
withMocks{
script.call(testImageName, [
dockerBakeTarget: 'another-target',
])
}

//final String expectedImageName = 'jenkins/' + testImageName
printCallStack()

// Then we expect a successful build with the code cloned
assertJobStatusSuccess()

// // With the common workflow run as expected
assertTrue(assertMethodCallContainsPattern('libraryResource','io/jenkins/infra/docker/Makefile'))
assertTrue(assertMethodCallContainsPattern('withEnv', "BUILD_DATE=${mockedSimpleDate}"))
assertTrue(assertMethodCallContainsPattern('sh','make lint'))
assertTrue(assertMethodCallContainsPattern('sh','make bake-build'))

assertTrue(assertMethodCallContainsPattern('sh', 'make bake-build'))
assertFalse(assertMethodCallContainsPattern('sh', 'make build'))
assertTrue(assertMethodCallContainsPattern('sh', 'make bake-deploy'))
assertFalse(assertMethodCallContainsPattern('sh', 'make deploy'))
// // And the environement variables set with the custom configuration values
assertTrue(assertMethodCallContainsPattern('withEnv', 'BAKE_TARGETPLATFORMS=linux/amd64'))
assertTrue(assertMethodCallContainsPattern('withEnv', 'IMAGE_DOCKERFILE=Dockerfile'))
assertTrue(assertMethodCallContainsPattern('withEnv', "DOCKER_BAKE_FILE=${defaultDockerBakeFile}"))
assertTrue(assertMethodCallContainsPattern('withEnv', 'DOCKER_BAKE_TARGET=another-target'))
// // And all mocked/stubbed methods have to be called
verifyMocks()
}
Expand Down Expand Up @@ -727,7 +764,7 @@ class BuildDockerAndPublishImageStepTests extends BaseTest {
assertJobStatusFailure()

// And the error message is shown
assertTrue(assertMethodCallContainsPattern('echo', 'ERROR: dockerBakeFile is not supported on windows.'))
assertTrue(assertMethodCallContainsPattern('echo', 'ERROR: docker bake is not (yet) supported on windows.'))
}

@Test
Expand Down
17 changes: 11 additions & 6 deletions vars/buildDockerAndPublishImage.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import java.util.Date
import java.text.DateFormat

// makecall is a function to concentrate all the call to 'make'
def makecall(String action, String imageDeployName, String targetOperationSystem, String specificDockerBakeFile) {
def makecall(String action, String imageDeployName, String targetOperationSystem, String specificDockerBakeFile, String dockerBakeTarget) {
final String bakefileContent = libraryResource 'io/jenkins/infra/docker/jenkinsinfrabakefile.hcl'
// Please note that "make deploy" and the generated bake deploy file uses the environment variable "IMAGE_DEPLOY_NAME"
if (isUnix()) {
if (! specificDockerBakeFile) {
specificDockerBakeFile = 'jenkinsinfrabakefile.hcl'
writeFile file: specificDockerBakeFile, text: bakefileContent
}
withEnv(["DOCKER_BAKE_FILE=${specificDockerBakeFile}", "IMAGE_DEPLOY_NAME=${imageDeployName}"]) {
withEnv([
"DOCKER_BAKE_FILE=${specificDockerBakeFile}",
"DOCKER_BAKE_TARGET=${dockerBakeTarget}",
"IMAGE_DEPLOY_NAME=${imageDeployName}"
]) {
sh 'export BUILDX_BUILDER_NAME=buildx-builder; docker buildx use "${BUILDX_BUILDER_NAME}" 2>/dev/null || docker buildx create --use --name="${BUILDX_BUILDER_NAME}"'
sh "make bake-$action"
}
Expand Down Expand Up @@ -46,6 +50,7 @@ def call(String imageShortName, Map userConfig=[:]) {
registryNamespace: '', // Empty by default (means "autodiscover based on the current controller")
unstash: '', // Allow to unstash files if not empty
dockerBakeFile: '', // Specify the path to a custom Docker Bake file to use instead of the default one
dockerBakeTarget: 'default', // Specify the target of a custom Docker Bake file to work with
]
// Merging the 2 maps - https://blog.mrhaki.com/2010/04/groovy-goodness-adding-maps-to-map_21.html
final Map finalConfig = defaultConfig << userConfig
Expand Down Expand Up @@ -101,7 +106,7 @@ def call(String imageShortName, Map userConfig=[:]) {
String operatingSystem = finalConfig.targetplatforms.split('/')[0]

if (operatingSystem == 'windows' && finalConfig.dockerBakeFile != '') {
echo 'ERROR: dockerBakeFile is not supported on windows.'
echo 'ERROR: docker bake is not (yet) supported on windows.'
currentBuild.result = 'FAILURE'
return
}
Expand Down Expand Up @@ -200,7 +205,7 @@ def call(String imageShortName, Map userConfig=[:]) {
} // stage

stage("Build ${imageName}") {
makecall('build', imageName, operatingSystem, finalConfig.dockerBakeFile)
makecall('build', imageName, operatingSystem, finalConfig.dockerBakeFile, finalConfig.dockerBakeTarget)
} //stage

// There can be 2 kind of tests: per image and per repository
Expand All @@ -212,7 +217,7 @@ def call(String imageShortName, Map userConfig=[:]) {
if (fileExists(testHarness)) {
stage("Test ${testName} for ${imageName}") {
withEnv(["TEST_HARNESS=${testHarness}"]) {
makecall('test', imageName, operatingSystem, finalConfig.dockerBakeFile)
makecall('test', imageName, operatingSystem, finalConfig.dockerBakeFile, finalConfig.dockerBakeTarget)
} // withEnv
} //stage
} else {
Expand Down Expand Up @@ -262,7 +267,7 @@ def call(String imageShortName, Map userConfig=[:]) {
infra.withDockerPushCredentials{
if (env.TAG_NAME || env.BRANCH_IS_PRIMARY) {
stage("Deploy ${imageName}") {
makecall('deploy', imageName, operatingSystem, finalConfig.dockerBakeFile)
makecall('deploy', imageName, operatingSystem, finalConfig.dockerBakeFile, finalConfig.dockerBakeTarget)
}
} // if
} // withDockerPushCredentials
Expand Down
1 change: 1 addition & 0 deletions vars/buildDockerAndPublishImage.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The following arguments are available for this function:
* String **registry**: (Optional, defaults to "" which defines the registry based on the current controller setup) Container registry to deploy this image to (Example: "ghcr.io", "python/2.7").
* String **unstash**: (Optional, default to "") Allow to restore files from a previously saved stash if not empty, should contain the name of the last stashed as per https://www.jenkins.io/doc/pipeline/steps/workflow-basic-steps/#unstash-restore-files-previously-stashed.
* String **dockerBakeFile**: (Optionan, default to "") Specify the path to a custom Docker Bake file to use instead of the default one
* String **dockerBakeTarget**: (Optional, default to "default") Specify the target of a custom Docker Bake file to work with

The lint phase generates a report when it fails, recorded by the hadolint tool in your Jenkins instance.

Expand Down

0 comments on commit 61da11f

Please sign in to comment.