diff --git a/Dockerfile b/Dockerfile index 5446b11..11df8ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,7 +61,7 @@ COPY --from=golang /app/database-image-task /usr/local/bin/database-image-task # Install necessary packages # - perl for docker-login # - bash for image-builder -RUN apk add --virtual --update-cache perl bash docker-cli jq envsubst && \ +RUN apk add --virtual --update-cache perl bash docker-cli docker-cli-buildx jq envsubst && \ rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/* # Put in docker credentials so we can do docker pushes diff --git a/createDumpSanitisedDB.gql b/createDumpSanitisedDB.gql index 217b8fc..410cfe0 100644 --- a/createDumpSanitisedDB.gql +++ b/createDumpSanitisedDB.gql @@ -33,6 +33,12 @@ mutation createDumpSanitisedDB { type: STRING optional: true }, + { + name: "BUILDER_PLATFORMS", + displayName: "OPTIONAL: Comma-separated platforms for the resulting image (defaults to linux/amd64)", + type: STRING + optional: true + }, { name: "BUILDER_MTK_YAML_BASE64", displayName: "OPTIONAL: The base64 encoded value of the mtk dump file to use (defaults to no config)", diff --git a/createDumpSanitisedDB_setDBVariables.gql b/createDumpSanitisedDB_setDBVariables.gql index 8f085ff..0953c62 100644 --- a/createDumpSanitisedDB_setDBVariables.gql +++ b/createDumpSanitisedDB_setDBVariables.gql @@ -35,6 +35,12 @@ mutation createDumpSanitisedDB { type: STRING optional: true }, + { + name: "BUILDER_PLATFORMS", + displayName: "OPTIONAL: Comma-separated platforms for the resulting image (defaults to linux/amd64)", + type: STRING + optional: true + }, { name: "BUILDER_MTK_YAML_BASE64", displayName: "OPTIONAL: The base64 encoded value of the mtk dump file to use (defaults to no config)", diff --git a/internal/builder/builder.go b/internal/builder/builder.go index b169ced..3ff54dd 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -31,6 +31,7 @@ type Builder struct { MTKYAML string `json:"mtkYAML"` ExtendedInsertRows string `json:"extendedInsertRows,omitempty"` DatabaseType string `json:"databaseType"` + Platforms string `json:"platforms"` Debug bool `json:"debug,omitempty"` MTK MTK `json:"mtk"` } @@ -60,6 +61,7 @@ func generateBuildValues(vars []variables.LagoonEnvironmentVariable) Builder { MTKYAML: checkVariable("BUILDER_MTK_YAML_BASE64", variables.GetEnv("BUILDER_MTK_YAML_BASE64", ""), vars), ExtendedInsertRows: checkVariable("BUILDER_MTK_EXTENDED_INSERT_ROWS", variables.GetEnv("BUILDER_MTK_EXTENDED_INSERT_ROWS", ""), vars), DatabaseType: dbType, + Platforms: checkVariable("BUILDER_PLATFORMS", variables.GetEnv("BUILDER_PLATFORMS", "linux/amd64"), vars), Debug: debug, } switch dbType { diff --git a/internal/builder/builder_test.go b/internal/builder/builder_test.go index 0535d26..681bdb8 100644 --- a/internal/builder/builder_test.go +++ b/internal/builder/builder_test.go @@ -53,6 +53,7 @@ func Test_generateValues(t *testing.T) { RegistryPassword: "regpass", RegistryHost: "reghost", DatabaseType: "mariadb", + Platforms: "linux/amd64", MTK: MTK{ Host: "dbhost", Username: "dbuser", @@ -94,6 +95,7 @@ func Test_generateValues(t *testing.T) { RegistryPassword: "regpass", RegistryHost: "reghost", DatabaseType: "mariadb", + Platforms: "linux/amd64", MTK: MTK{ Host: "dbhost", Username: "dbuser", @@ -136,6 +138,7 @@ func Test_generateValues(t *testing.T) { RegistryPassword: "regpass", RegistryHost: "reghost", DatabaseType: "mariadb", + Platforms: "linux/amd64", MTK: MTK{ Host: "dbrrhost1", Username: "dbuser", @@ -181,6 +184,7 @@ func Test_generateValues(t *testing.T) { RegistryPassword: "regpass", RegistryHost: "reghost", DatabaseType: "mariadb", + Platforms: "linux/amd64", MTK: MTK{ Host: "dbhostcentral", Username: "dbusercentral", @@ -223,6 +227,7 @@ func Test_generateValues(t *testing.T) { RegistryPassword: "regpass", RegistryHost: "reghost", DatabaseType: "mariadb", + Platforms: "linux/amd64", MTK: MTK{ Host: "dbrrhost1", Username: "mariadbuser", @@ -265,6 +270,7 @@ func Test_generateValues(t *testing.T) { RegistryHost: "reghost", Debug: true, DatabaseType: "mariadb", + Platforms: "linux/amd64", MTK: MTK{ Host: "dbhost", Username: "dbuser", @@ -306,6 +312,7 @@ func Test_generateValues(t *testing.T) { RegistryPassword: "regpass", RegistryHost: "reghost", DatabaseType: "mysql", + Platforms: "linux/amd64", MTK: MTK{ Host: "dbhost", Username: "dbuser", @@ -349,6 +356,7 @@ func Test_generateValues(t *testing.T) { RegistryPassword: "regpass", RegistryHost: "reghost", DatabaseType: "mariadb", + Platforms: "linux/amd64", MTK: MTK{ Host: "dbhost", Username: "dbuser", diff --git a/mariadb-image-builder b/mariadb-image-builder index 3b99042..caf1627 100644 --- a/mariadb-image-builder +++ b/mariadb-image-builder @@ -142,18 +142,49 @@ done # these have to be the same base `mariadb/mysql` version to work (ie mariadb:10.6 as the builder, and uselagoon/mariadb-10.6-drupal:latest as the clean resulting image) -# build the image, but exit on error +BUILDER_PLATFORMS=$(echo "$IMAGE_BUILD_DATA" | jq -rc '.platforms') +BUILDER_PUSH_TAGS=$(echo "$IMAGE_BUILD_DATA" | jq -rc '.pushTags') + +# Log in to the registry before building; buildx pushes during the build for multi-arch manifests. +# Reading credentials order is: +# - ${XDG_RUNTIME_DIR}/containers/auth.json (and writing) +# - $HOME/.docker/config.json +set -o errexit +echo $(echo "$IMAGE_BUILD_DATA" | jq -rc '.registryPassword') | docker login $(echo "$IMAGE_BUILD_DATA" | jq -rc '.registryHost') -u "$(echo "$IMAGE_BUILD_DATA" | jq -rc '.registryUsername')" --password-stdin + +build_tags=() +if [ "$BUILDER_PUSH_TAGS" == "both" ] || [ "$BUILDER_PUSH_TAGS" == "latest" ]; then + build_tags+=(-t "${BUILDER_BACKUP_IMAGE_NAME}:latest") +fi +if [ "$BUILDER_PUSH_TAGS" == "both" ] || [ "$BUILDER_PUSH_TAGS" == "default" ]; then + build_tags+=(-t "${backup_image_full}") +fi +if [ ${#build_tags[@]} -eq 0 ]; then + echo "No tags configured to push (BUILDER_PUSH_TAGS=${BUILDER_PUSH_TAGS})" + exit 1 +fi + +# Ensure a buildx builder exists on the remote docker host for multi-platform builds. +if ! docker buildx inspect lagoon-multiarch >/dev/null 2>&1; then + docker buildx create --name lagoon-multiarch --driver docker-container --use --bootstrap +else + docker buildx use lagoon-multiarch +fi + if [ "$DEBUG" == "true" ]; then set -o xtrace fi -set -o errexit # template out the my.cnf file for the images envsubst < my.cnf.tpl > my.cnf envsubst < import.my.cnf.tpl > import.my.cnf -docker build --network=host --build-arg BUILDER_IMAGE="$(echo "$IMAGE_BUILD_DATA" | jq -rc '.sourceImage')" \ +echo "Building and pushing multi-arch image for platforms: ${BUILDER_PLATFORMS}" +docker buildx build --network=host \ + --platform "${BUILDER_PLATFORMS}" \ + --build-arg BUILDER_IMAGE="$(echo "$IMAGE_BUILD_DATA" | jq -rc '.sourceImage')" \ --build-arg CLEAN_IMAGE="$(echo "$IMAGE_BUILD_DATA" | jq -rc '.cleanImage')" \ - -t ${backup_image_full} \ - -t "${BUILDER_BACKUP_IMAGE_NAME}:latest" . + "${build_tags[@]}" \ + --push \ + . if [ "$DEBUG" == "true" ]; then set +o xtrace fi @@ -168,34 +199,19 @@ previousStepEnd=${currentStepEnd} echo -e "##############################################\nBEGIN Save new container to registry\n##############################################" echo "Current time: $(date +"%Y-%m-%d %H:%M:%S")" echo +echo "Multi-arch images pushed during build for platforms: ${BUILDER_PLATFORMS}" -# Log in to dockerhub or other registry - -# Reading credentials order is: -# - ${XDG_RUNTIME_DIR}/containers/auth.json (and writing) -# - $HOME/.docker/config.json - -# BUILDER_REGISTRY_USERNAME is the name to log in to the registry -# BUILDER_REGISTRY_PASSWORD is the password of the user -# BUILDER_REGISTRY_HOST is required if not using dockerhub, eg: `quay.io` -set -o errexit -echo $(echo "$IMAGE_BUILD_DATA" | jq -rc '.registryPassword') | docker login $(echo "$IMAGE_BUILD_DATA" | jq -rc '.registryHost') -u "$(echo "$IMAGE_BUILD_DATA" | jq -rc '.registryUsername')" --password-stdin -set +o errexit - -BUILDER_PUSH_TAGS=$(echo "$IMAGE_BUILD_DATA" | jq -rc '.pushTags') -# Push the image to remote -if [ "$BUILDER_PUSH_TAGS" == "both" ] || [ "$BUILDER_PUSH_TAGS" == "latest" ]; then - docker push "${BUILDER_BACKUP_IMAGE_NAME}:latest" - if [ "$BUILDER_REMOVE_IMAGE" != "skip" ]; then - docker rmi --force "${BUILDER_BACKUP_IMAGE_NAME}:latest" +# Clean up pushed images and build cache from the remote docker host to free disk space. +# BUILDER_REMOVE_IMAGE=skip can be set to retain images for debugging. +if [ "${BUILDER_REMOVE_IMAGE}" != "skip" ]; then + if [ "$BUILDER_PUSH_TAGS" == "both" ] || [ "$BUILDER_PUSH_TAGS" == "latest" ]; then + docker rmi --force "${BUILDER_BACKUP_IMAGE_NAME}:latest" 2>/dev/null || true fi -fi - -if [ "$BUILDER_PUSH_TAGS" == "both" ] || [ "$BUILDER_PUSH_TAGS" == "default" ]; then - docker push "${backup_image_full}" - if [ "$BUILDER_REMOVE_IMAGE" != "skip" ]; then - docker rmi --force "${backup_image_full}" + if [ "$BUILDER_PUSH_TAGS" == "both" ] || [ "$BUILDER_PUSH_TAGS" == "default" ]; then + docker rmi --force "${backup_image_full}" 2>/dev/null || true fi + # buildx --push does not load images into the local daemon, but the builder retains cache layers. + docker buildx prune --builder lagoon-multiarch -f 2>/dev/null || true fi echo