diff --git a/.compile_binaries b/.compile_binaries index b535ae9..f657d76 100755 --- a/.compile_binaries +++ b/.compile_binaries @@ -19,12 +19,6 @@ build_linux() { do cp "shellcheck" "deploy/shellcheck-$tag.linux-x86_64"; done - - # Linux Alpine based Docker image - name="$DOCKER_BASE-alpine" - DOCKER_BUILDS="$DOCKER_BUILDS $name" - docker build -f Dockerfile -t "$name:current" --target alpine . - docker run "$name:current" sh -c 'shellcheck --version' } build_aarch64() { diff --git a/.multi_arch_docker b/.multi_arch_docker new file mode 100755 index 0000000..93c0859 --- /dev/null +++ b/.multi_arch_docker @@ -0,0 +1,107 @@ +#!/bin/bash +# This script builds and deploys multi-architecture docker images from the +# binaries previously built and deployed to GCS by the Travis pipeline. + +function multi_arch_docker::install_docker_buildx() { + # Install up-to-date version of docker, with buildx support. + local -r docker_apt_repo='https://download.docker.com/linux/ubuntu' + curl -fsSL "${docker_apt_repo}/gpg" | sudo apt-key add - + local -r os="$(lsb_release -cs)" + sudo add-apt-repository "deb [arch=amd64] $docker_apt_repo $os stable" + sudo apt-get update + sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce + + # Enable docker daemon experimental support (for 'pull --platform'). + local -r config='/etc/docker/daemon.json' + if [[ -e "$config" ]]; then + sudo sed -i -e 's/{/{ "experimental": true, /' "$config" + else + echo '{ "experimental": true }' | sudo tee "$config" + fi + sudo systemctl restart docker + + # Install QEMU multi-architecture support for docker buildx. + docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + + # Instantiate docker buildx builder with multi-architecture support. + export DOCKER_CLI_EXPERIMENTAL=enabled + docker buildx create --name mybuilder + docker buildx use mybuilder + # Start up buildx and verify that all is OK. + docker buildx inspect --bootstrap +} + +# Log in to Docker Hub for deployment. +function multi_arch_docker::login_to_docker_hub() { + echo "$DOCKER_PASSWORD" | docker login -u="$DOCKER_USERNAME" --password-stdin +} + +# Run buildx build and push. Passed in arguments augment the command line. +function multi_arch_docker::buildx() { + mkdir -p /tmp/empty + docker buildx build \ + --platform "${DOCKER_PLATFORMS// /,}" \ + --push \ + --progress plain \ + -f Dockerfile.multi-arch \ + "$@" \ + /tmp/empty + rmdir /tmp/empty +} + +# Build and push plain and alpine docker images for all tags. +function multi_arch_docker::build_and_push_all() { + for tag in $TAGS; do + multi_arch_docker::buildx -t "$DOCKER_BASE:$tag" --build-arg "tag=$tag" + multi_arch_docker::buildx -t "$DOCKER_BASE-alpine:$tag" \ + --build-arg "tag=$tag" --target alpine + done +} + +# Test all pushed docker images. +function multi_arch_docker::test_all() { + printf '%s\n' "#!/bin/sh" "echo 'hello world'" > myscript + + for platform in $DOCKER_PLATFORMS; do + for tag in $TAGS; do + for ext in '-alpine' ''; do + image="${DOCKER_BASE}${ext}:${tag}" + msg="Testing docker image $image on platform $platform" + line="${msg//?/=}" + printf '\n%s\n%s\n%s\n' "${line}" "${msg}" "${line}" + docker pull -q --platform "$platform" "$image" + if [ -n "$ext" ]; then + echo -n "Image architecture: " + docker run --rm --entrypoint /bin/sh "$image" -c 'uname -m' + version=$(docker run --rm "$image" shellcheck --version \ + | grep 'version:') + else + version=$(docker run --rm "$image" --version | grep 'version:') + fi + version=${version/#version: /v} + echo "shellcheck version: $version" + if [[ ! ("$tag" =~ ^(latest|stable)$) && "$tag" != "$version" ]]; then + echo "Version mismatch: shellcheck $version tagged as $tag" + exit 1 + fi + if [ -n "$ext" ]; then + docker run --rm -v "$PWD:/mnt" -w /mnt "$image" shellcheck myscript + else + docker run --rm -v "$PWD:/mnt" "$image" myscript + fi + done + done + done +} + +function multi_arch_docker::main() { + export DOCKER_PLATFORMS='linux/amd64' + DOCKER_PLATFORMS+=' linux/arm64' + DOCKER_PLATFORMS+=' linux/arm/v6' + + multi_arch_docker::install_docker_buildx + multi_arch_docker::login_to_docker_hub + multi_arch_docker::build_and_push_all + set +x + multi_arch_docker::test_all +} diff --git a/.travis.yml b/.travis.yml index 43f0a35..6c601a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,13 +6,19 @@ services: jobs: include: - - env: BUILD=linux + - stage: Test + env: BUILD=linux - env: BUILD=windows - env: BUILD=armv6hf - env: BUILD=aarch64 - env: BUILD=osx os: osx + - stage: Deploy docker image + script: + - source ./.multi_arch_docker + - set -ex; multi_arch_docker::main; set +x + before_install: | DOCKER_BASE="$DOCKER_USERNAME/shellcheck" DOCKER_BUILDS="" @@ -28,18 +34,6 @@ script: - set -ex; build_"$BUILD"; set +x; - ./.prepare_deploy -after_success: | - if [ "$BUILD" = "linux" ]; then - docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" - for repo in $DOCKER_BUILDS; do - for tag in $TAGS; do - echo "Deploying $repo:current as $repo:$tag..."; - docker tag "$repo:current" "$repo:$tag" || exit 1; - docker push "$repo:$tag" || exit 1; - done; - done; - fi - after_failure: | id pwd @@ -57,4 +51,5 @@ deploy: local_dir: deploy on: repo: koalaman/shellcheck + condition: $TRAVIS_BUILD_STAGE_NAME = Test all_branches: true diff --git a/Dockerfile b/Dockerfile index 0ce807d..2f8f79e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,13 +21,6 @@ RUN cabal build Paths_ShellCheck && \ RUN mkdir -p /out/bin && \ cp shellcheck /out/bin/ -# Resulting Alpine image -FROM alpine:latest AS alpine -LABEL maintainer="Vidar Holen " -COPY --from=build /out / - -# DELETE-MARKER (Remove everything below to keep the alpine image) - # Resulting ShellCheck image FROM scratch LABEL maintainer="Vidar Holen " diff --git a/Dockerfile.multi-arch b/Dockerfile.multi-arch new file mode 100644 index 0000000..193e762 --- /dev/null +++ b/Dockerfile.multi-arch @@ -0,0 +1,26 @@ +# Alpine image +FROM alpine:latest AS alpine +LABEL maintainer="Vidar Holen " +ARG tag + +# Put the right binary for each architecture into place for the +# multi-architecture docker image. +RUN set -x; \ + arch="$(uname -m)"; \ + echo "arch is $arch"; \ + if [ "${arch}" = 'armv7l' ]; then \ + arch='armv6hf'; \ + fi; \ + url_base='https://shellcheck.storage.googleapis.com/'; \ + tar_file="shellcheck-${tag}.linux.${arch}.tar.xz"; \ + wget "${url_base}${tar_file}" -O - | tar xJf -; \ + mv "shellcheck-${tag}/shellcheck" /bin/; \ + rm -rf "shellcheck-${tag}"; \ + ls -laF /bin/shellcheck + +# ShellCheck image +FROM scratch +LABEL maintainer="Vidar Holen " +WORKDIR /mnt +COPY --from=alpine /bin/shellcheck /bin/ +ENTRYPOINT ["/bin/shellcheck"]