Mit dieser Bitbucket Pipeline kannst du Multi Architektur Docker Images Bauen und im Docker Hub veröffentlichen sowie bei Fehlern und Erfolg Discord Webhook Nachrichten senden. Die Pipeline verwendet die Container Structure Tests, um die Docker Images zu testen. Die Tests werden in einem JUnit-Format ausgegeben und können in Bitbucket Pipelines angezeigt werden.
Für die Pipeline verwende ich mein eigenes Docker Image, welches auch über diese Pipeline gebaut wird. Das Docker Image ist auf Docker Hub verfügbar und kann in der Pipeline verwendet werden.
wimdevgroup/docker-buildx-pipeline:latest
Die Pipeline benötigt einen Bitbucket Runner, um die Images zu bauen da Bitbucket das bauen von multi architektur Images nicht unterstützt. Daher wird der bau prozess auf einen eigenen Runner/Server ausgelagert.
Die verwendung vom Discord Webhook ist optional und kann entfernt werden. Die Pipeline ohne Discord Webhook werde ich auch zur verfügung stellen.
Voraussetzungen
Docker Hub Account
Bitbucket
Self Hosted Bitbucket Runner
Ein System mit Docker und Docker Buildx auf dem der Bitbucket Runner läuft
#!/bin/bash# @see https://stackoverflow.com/a/20460402stringContains(){case$2in*$1*)return0;;*)return1;;esac;}if[-z${DOCKERHUB_USERNAME+x}];thenecho"[ERROR] DOCKERHUB_USERNAME is not provided. Username to a hub.docker.com profile expected"exit1fi;if![-x"$(command-venvsubst)"];thenecho"[ERROR] envsubst is not installed."exit1fi;if![-x"$(command-vcontainer-structure-test)"];thenecho"[ERROR] container-structure-test is not installed."exit1fi;if[["$@"==*"--push"]];thenif[-z${DOCKERHUB_PASSWORD+x}];thenecho"[ERROR] DOCKERHUB_PASSWORD is not provided. Password to the hub.docker.com profile ${DOCKERHUB_USERNAME} expected"exit1fi;echo"[INFO] Logging into hub.docker.com"echo${DOCKERHUB_PASSWORD}|dockerlogin--username"${DOCKERHUB_USERNAME}"--password-stdin
echo"[INFO] Logged into hub.docker.com Successfully"fiecho"[INFO] Check for cloud buildx builder version"if!(dockerbuildxversion);thenecho"[ERROR] Failed to Check for cloud buildx builder version"exit1fi;echo"[INFO] Check for cloud buildx builder"if!(dockerbuildxls);thenecho"[ERROR] Failed to Check for cloud buildx builder"exit1fi;echo"[INFO] Check for cloud buildx builder"if!(dockerbuildxls);thenecho"[ERROR] Failed to Check for cloud buildx builder"exit1fi;containerdir=container
if![-z"$1"]&&[-d"${containerdir}/$1"];thencontainerdir=${containerdir}/$1fiecho"[INFO] Search for images in ${containerdir}"fordockerfilein$(find"${containerdir}"-typef-name.Dockerfile-execls{}\;);dodockerdir=$(dirname$dockerfile)versionfile="${dockerdir}/.version"testreportdir="${containerdir}/test-results/"mkdir-p"${testreportdir}"if![-f"$versionfile"];thenecho"[ERROR] No version file (${versionfile}) created"exit1fi;if[$(cat"$versionfile"|wc-m)=="0"];thenecho"[INFO] No version defined in ${versionfile}"fi;VERSION=$(cat$versionfile)DOCKERIMAGE=$(basename$dockerdir)echo"[INFO] Process ${DOCKERIMAGE}@${VERSION} (${dockerdir})"fortagfilein$(find"$dockerdir"-maxdepth1-typef-name'.*.env'-execls{}\;);dotagfilename=$(basename"$tagfile".env)TAG=${tagfilename:1}echo"[INFO] Process ${DOCKERIMAGE}:${TAG}"echo"[INFO] Reset build directory"if[-d.build];thenrm-rf.build
fi;if!(mkdir.build);thenecho"[ERROR] Cannot create build directory"exit1fi;echo"[INFO] Process image template"envcontent=$(grep-v"^#""$tagfile"|sed-e's/^\\s*(.*?)\\s*$/\\1/'|sed-e's/^$//')if[-z${envcontent+x}-o$(echo"$envcontent"|wc-m)-lt2];thenecho"[INFO] Empty environment provided"cat"$dockerfile"|TAG=$TAGenvsubst>.build/Dockerfile
elsecat"$dockerfile"|(export$(echo"$envcontent"|xargs)&&TAG=$TAGenvsubst>.build/Dockerfile)fi;if[$(ls-1"${dockerdir}"|wc-m)=="0"];thenecho"[INFO] No data files to copy"elseif!(cp-a${dockerdir}/*.build);thenecho"[ERROR] Cannot copy data to build directory"exit1elseecho"[INFO] Copy data to build directory"fi;fi;if[["$VERSION"=~^[0-9]+$]];thenecho"VERSION enthält nur Zahlen."if[["${TAG}"=="latest"]];thentagversion="${VERSION}"taglatest="latest"echo"[INFO] Tag latest ${tagversion}"elsetagversion="${TAG}-${VERSION}"taglatest="${TAG}-latest"echo"[INFO] Tag ${tagversion}"fi;elsetagversion="${TAG}"fiecho"[INFO] Build image"if!(dockerbuildxbuild--builderbuilder--tag"${DOCKERHUB_USERNAME}/${DOCKERIMAGE}:${tagversion}"--platformlinux/amd64,linux/arm64--push.build);thenecho"[ERROR] Failed building image"exit1fi;echo"[INFO] Prepare test files"[[!-d"${containerdir}/tests"]]||rm-rf"${containerdir}/tests"mkdir-p"${containerdir}/tests/"fortestfilein$(find"${containerdir}/.tests"-typef-name'*.yml'-execls{}\;);dotestfile_basename=$(basename"${testfile}")# number of words determines, whether a version is in the file nametestfile_basename_words="${testfile_basename//./ }"testfile_basename_n_words=$(echo"${testfile_basename_words}"|wc-w)if[[${testfile_basename_n_words}-ne2]];then# file has at least two dots and therefore has a tag in itif!stringContains".${TAG}.yml""${testfile_basename}";then# the filename has not the tag in question in it? then skipecho"[INFO] Skip ${testfile} as it seems not to match the tag, that needs to be tested (testfile_basename_n_words: ${testfile_basename_n_words} ; testfile_basename_words: ${testfile_basename_words} ; TAG: ${TAG} ; testfile_basename: ${testfile_basename})"continuefiecho"[INFO] Take ${testfile} as it seems to match the tag, that needs to be tested (testfile_basename_n_words: ${testfile_basename_n_words} ; testfile_basename_words: ${testfile_basename_words} ; TAG: ${TAG} ; testfile_basename: ${testfile_basename})"elseecho"[INFO] Take ${testfile} as it seems to be used on any tag (testfile_basename_n_words: ${testfile_basename_n_words} ; testfile_basename_words: ${testfile_basename_words})"fiif[-z${envcontent+x}-o$(echo"$envcontent"|wc-m)-lt2];then# no env vars to replacecat"${testfile}"|TAG=$TAGenvsubst>"${testfile/.tests/tests}"elsecat"${testfile}"|(export$(echo"$envcontent"|xargs)&&TAG=$TAGenvsubst>"${testfile/.tests/tests}")fi;donefortestfilein$(find"${containerdir}/tests"-typef-name'*.yml'-execls{}\;);dotestfilename=$(basename"$testfile".yml)reportfile=${testreportdir}/report-${DOCKERIMAGE}-${tagfilename}-${testfilename}.xml
echo"[INFO] Test image against ${testfilename}"if!(container-structure-testtest--image"${DOCKERHUB_USERNAME}/${DOCKERIMAGE}:${tagversion}"--config"${testfile}"--outputjunit--test-report"${reportfile}")thenecho"[ERROR] Failed testing image"exit1fi;donedone;done;
#!/bin/bash# @see https://stackoverflow.com/a/20460402stringContains(){case$2in*$1*)return0;;*)return1;;esac;}if[-z${DISCORD_WEBHOOK_URL+x}];thenecho"[ERROR] DISCORD_WEBHOOK_URL is not provided. Webhook to a discord.com channel expected."exit1fi;if[-z${BITBUCKET_WORKSPACE+x}];thenecho"[ERROR] BITBUCKET_WORKSPACE is not provided."exit1fi;if[-z${BITBUCKET_REPO_FULL_NAME+x}];thenecho"[INFO] BITBUCKET_REPO_FULL_NAME is not provided."BITBUCKET_REPO_FULL_NAME="unknown"fi;if[-z${container+x}];thenecho"[INFO] container is not provided."DOCKER_IMAGE="All containers"elseDOCKER_IMAGE=$containerfi;if[-z${PIPELINE_STATE+x}];thenecho"[ERROR] $PIPELINE_STATE is not provided."exit1fi;if[$PIPELINE_STATE-eq1];thenecho"[INFO] Pipeline is successful"PIPELINE_STATE="successful"PIPELINE_STATE_EMOTE="✅"COLOR=3066993elif[$PIPELINE_STATE-eq2];thenecho"[INFO] Pipeline is failed"PIPELINE_STATE="failed"PIPELINE_STATE_EMOTE="❌"COLOR=15158332elif[$PIPELINE_STATE-eq3];thenecho"[INFO] Automatic tag release"PIPELINE_STATE="Automatic tag release"PIPELINE_STATE_EMOTE="🔄"COLOR=3447003elseecho"[INFO] Pipeline is in progress"PIPELINE_STATE="in progress"PIPELINE_STATE_EMOTE="🔄"COLOR=3447003fi# Textvariablen und TestdatenUSERNAME="$BITBUCKET_WORKSPACE Bitbucket Pipeline for Repository $BITBUCKET_REPO_FULL_NAME"MESSAGE="@everyone Pipeline $PIPELINE_STATE!"DATA_TITLE="$PIPELINE_STATE_EMOTE Pipeline Status $PIPELINE_STATE$PIPELINE_STATE_EMOTE"DATA="**Docker Image**\\n$DOCKER_IMAGE\\n\u200B\\n**Status**\\n$PIPELINE_STATE\\n\u200B\\n[$BITBUCKET_REPO_FULL_NAME](https://bitbucket.org/$BITBUCKET_REPO_FULL_NAME/pipelines/results/$BITBUCKET_PIPELINE_UUID)"IMAGE_URL="https://images.wimwenigerkind.com/wimwenigerkind-transparent-icon.png"# Ersetze dies mit der Bild-URL für das Profilbild# JSON-Payload ausklappen und klar strukturiert ins Skript integrierenPAYLOAD="{ \"username\": \"$USERNAME\", \"avatar_url\": \"$IMAGE_URL\", \"content\": \"$MESSAGE\", \"embeds\": [ { \"title\": \"$DATA_TITLE\", \"description\": \"$DATA\", \"color\": $COLOR } ]}"# Webhook sendencurl-XPOST-H"Content-Type: application/json"-d"$PAYLOAD"$DISCORD_WEBHOOK_URLecho"Webhook mit benutzerdefiniertem Profilbild gesendet!"
<xsl:stylesheetversion="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:outputomit-xml-declaration="yes"indent="yes"/><xsl:strip-spaceelements="*"/><!-- recursive through all nodes --><xsl:templatematch="node()|@*"><xsl:copy><xsl:apply-templatesselect="node()|@*"/></xsl:copy></xsl:template><!-- copy <testsuite> and add attributes "failure" and "tests" based in the testsuite children nodes --><xsl:templatematch="testsuite"><testsuitefailures="{count(testcase/failure)}"tests="{count(testcase)}"><xsl:apply-templatesselect="node()|@*"/></testsuite></xsl:template></xsl:stylesheet>