From 141af7909907e04b124e691d3cd6fc7c32da2207 Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Wed, 7 Aug 2024 16:44:15 +0200 Subject: [PATCH] Build multi-arch image including Windows (#5) * Build multi-arch image including Windows * Sync workflows from testcontainers/moby-ryuk * Fix workflow reference See https://github.com/testcontainers/moby-ryuk/pull/138 * Sync with the workflows from moby-ryuk --- .github/workflows/build-docker-image.yml | 255 +++++++++++++++++++++ .github/workflows/docker-build.yml | 24 -- .github/workflows/publish-docker-image.yml | 147 ++++++++++++ Dockerfile | 11 - README.md | 4 +- RELEASING.md | 13 ++ architectures.txt | 8 + go.mod | 8 +- go.sum | 12 +- linux/Dockerfile | 24 ++ supported-architectures.txt | 8 + windows/Dockerfile | 25 ++ 12 files changed, 492 insertions(+), 47 deletions(-) create mode 100644 .github/workflows/build-docker-image.yml delete mode 100644 .github/workflows/docker-build.yml create mode 100644 .github/workflows/publish-docker-image.yml delete mode 100644 Dockerfile create mode 100644 RELEASING.md create mode 100644 architectures.txt create mode 100644 linux/Dockerfile create mode 100644 supported-architectures.txt create mode 100644 windows/Dockerfile diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml new file mode 100644 index 0000000..2831376 --- /dev/null +++ b/.github/workflows/build-docker-image.yml @@ -0,0 +1,255 @@ +name: Test & Build multi-arch Docker Image + +on: + push: + branches: [ main ] + pull_request: + +env: + IMAGE_REPOSITORY: testcontainers/helloworld + +jobs: + test-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - run: go version + + - name: go-mod verify + run: go mod verify + + - name: go-mod tidy + run: go mod tidy + + - name: go-build + env: + GOOS: linux + run: go build + + - name: go-test + run: go test -v ./... + + test-windows: + runs-on: windows-2022 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - run: go version + + - name: go-mod verify + run: go mod verify + + - name: go-mod tidy + run: go mod tidy + + - name: go-build + env: + GOOS: windows + run: go build + +# TODO enable tests on Windows +# Currently doesn't succeed, see https://github.com/testcontainers/testcontainers-go/issues/948 +# - name: go-test +# run: go test -v ./... + + build-image-linux: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - run: go version + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@a64d0487d7069df33b279515d35d60fa80e2ea62 + with: + images: ${{ env.IMAGE_REPOSITORY }} + tags: | + type=sha,format=long + flavor: | + suffix=-linux + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + + - name: Docker info + run: docker info + - name: Buildx inspect + run: docker buildx inspect + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + file: linux/Dockerfile + platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x,linux/386,linux/arm/v6 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + build-image-windows: + strategy: + matrix: + os-version: + - ltsc2019 + - ltsc2022 + runs-on: windows-2022 + if: github.ref == 'refs/heads/main' + steps: + - uses: actions/checkout@v4 + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@a64d0487d7069df33b279515d35d60fa80e2ea62 + with: + images: ${{ env.IMAGE_REPOSITORY }} + tags: | + type=sha,format=long + flavor: | + suffix=-windows.${{ matrix.os-version }} + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Docker info + run: docker info + + - name: Build image + run: | + docker build -f windows/Dockerfile --build-arg BASE_IMAGE=mcr.microsoft.com/windows/nanoserver:${{ matrix.os-version }} -t ${{ steps.meta.outputs.tags }} . + + - name: Push image + run: | + docker push ${{ steps.meta.outputs.tags }} + + publish-multi-arch-image: + needs: + - test-linux + - test-windows + - build-image-linux + - build-image-windows + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + steps: + - uses: actions/checkout@v4 + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@a64d0487d7069df33b279515d35d60fa80e2ea62 + with: + images: ${{ env.IMAGE_REPOSITORY }} + tags: | + type=sha,format=long + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + + - name: Docker info + run: docker info + - name: Buildx inspect + run: docker buildx inspect + + - name: Get os version for nanoserver:ltsc2019 + run: >- + full_version=$(docker manifest inspect mcr.microsoft.com/windows/nanoserver:ltsc2019 | jq -r '.manifests[]|.platform|."os.version"'| sed 's@.*:@@') || true; + echo "OS_VERSION_ltsc2019=${full_version}" >> $GITHUB_ENV; + + - name: Get os version for nanoserver:ltsc2022 + run: >- + full_version=$(docker manifest inspect mcr.microsoft.com/windows/nanoserver:ltsc2022 | jq -r '.manifests[]|.platform|."os.version"'| sed 's@.*:@@') || true; + echo "OS_VERSION_ltsc2022=${full_version}" >> $GITHUB_ENV; + + - name: Docker Manifest + run: >- + target_image=${{ steps.meta.outputs.tags }}; + linux_manifest=$(docker manifest inspect ${target_image}-linux); + linux_digests=$(docker manifest inspect ${target_image}-linux | jq -r '.manifests[].digest'); + manifest_list=${linux_digests//sha256:/${target_image%%:*}@sha256:}; + manifest_list+=" ${target_image}-windows.ltsc2019"; + manifest_list+=" ${target_image}-windows.ltsc2022"; + docker manifest create ${target_image} ${manifest_list}; + docker manifest annotate \ + --os-version ${OS_VERSION_ltsc2019} \ + --os windows \ + --arch amd64 \ + ${target_image} "${target_image}-windows.ltsc2019"; + docker manifest annotate \ + --os-version ${OS_VERSION_ltsc2022} \ + --os windows \ + --arch amd64 \ + ${target_image} "${target_image}-windows.ltsc2022"; + docker manifest push ${target_image}; + + check-published-image: + runs-on: ubuntu-latest + needs: + - publish-multi-arch-image + if: github.ref == 'refs/heads/main' + steps: + - uses: actions/checkout@v4 + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@a64d0487d7069df33b279515d35d60fa80e2ea62 + with: + images: ${{ env.IMAGE_REPOSITORY }} + tags: | + type=sha,format=long + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Print multi-arch manifest + run: | + docker manifest inspect --verbose ${{ steps.meta.outputs.tags }} > manifest_complete.json + cat manifest_complete.json | jq ".[].Descriptor.platform" | jq "(.architecture + \"_\" + .os + \"_\") + .\"os.version\"" | sort | uniq > architectures_actual.txt + + - name: Check multi-arch manifest + run: | + cat architectures_actual.txt + cat supported-architectures.txt | sort | uniq > architectures_expected.txt + # we're counting the entries only, because windows os.version numbers change too frequently + # exclude manifest entries with "unknown" architectures + diff <(echo "$(cat architectures_expected.txt | wc -l)") <(echo "$(cat architectures_actual.txt | grep -v unknown_unknown_ | wc -l)") + + build: + runs-on: ubuntu-latest + needs: + - test-linux + - test-windows + steps: + - name: Finish + run: | + echo "All tests passed" diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml deleted file mode 100644 index 708aa45..0000000 --- a/.github/workflows/docker-build.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Docker build - -on: - push: - branches: - - main - - # Build for any PRs. - pull_request: - -env: - IMAGE_NAME: helloworld - -jobs: - # Build the container - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Build image - run: docker build -t testcontainers/helloworld:latest . --file Dockerfile - diff --git a/.github/workflows/publish-docker-image.yml b/.github/workflows/publish-docker-image.yml new file mode 100644 index 0000000..9a80058 --- /dev/null +++ b/.github/workflows/publish-docker-image.yml @@ -0,0 +1,147 @@ +name: Release multi-arch Docker Image + +on: + release: + types: [ published ] + +env: + IMAGE_REPOSITORY: testcontainers/helloworld + +jobs: + release-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + + - name: Docker info + run: docker info + - name: Buildx inspect + run: docker buildx inspect + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + file: linux/Dockerfile + platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x,linux/386,linux/arm/v6 + push: true + tags: ${{ env.IMAGE_REPOSITORY }}:${{ github.event.release.tag_name }}-linux + + release-windows: + strategy: + matrix: + os-version: + - ltsc2019 + - ltsc2022 + runs-on: windows-2022 + steps: + - uses: actions/checkout@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Docker info + run: docker info + + - name: Build image + run: | + docker build -f windows/Dockerfile --build-arg BASE_IMAGE=mcr.microsoft.com/windows/nanoserver:${{ matrix.os-version }} -t ${{ env.IMAGE_REPOSITORY }}:${{ github.event.release.tag_name }}-windows.${{ matrix.os-version }} . + + - name: Push image + run: | + docker push ${{ env.IMAGE_REPOSITORY }}:${{ github.event.release.tag_name }}-windows.${{ matrix.os-version }} + + release: + needs: + - release-linux + - release-windows + runs-on: ubuntu-latest + steps: + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 + + - name: Docker info + run: docker info + - name: Buildx inspect + run: docker buildx inspect + + - name: Get os version for nanoserver:ltsc2019 + run: >- + full_version=$(docker manifest inspect mcr.microsoft.com/windows/nanoserver:ltsc2019 | jq -r '.manifests[]|.platform|."os.version"'| sed 's@.*:@@') || true; + echo "OS_VERSION_ltsc2019=${full_version}" >> $GITHUB_ENV; + + - name: Get os version for nanoserver:ltsc2022 + run: >- + full_version=$(docker manifest inspect mcr.microsoft.com/windows/nanoserver:ltsc2022 | jq -r '.manifests[]|.platform|."os.version"'| sed 's@.*:@@') || true; + echo "OS_VERSION_ltsc2022=${full_version}" >> $GITHUB_ENV; + + - name: Docker Manifest + run: >- + target_image=${{ env.IMAGE_REPOSITORY }}:${{ github.event.release.tag_name }}; + linux_manifest=$(docker manifest inspect ${target_image}-linux); + linux_digests=$(docker manifest inspect ${target_image}-linux | jq -r '.manifests[].digest'); + manifest_list=${linux_digests//sha256:/${target_image%%:*}@sha256:}; + manifest_list+=" ${target_image}-windows.ltsc2019"; + manifest_list+=" ${target_image}-windows.ltsc2022"; + docker manifest create ${target_image} ${manifest_list}; + docker manifest annotate \ + --os-version ${OS_VERSION_ltsc2019} \ + --os windows \ + --arch amd64 \ + ${target_image} "${target_image}-windows.ltsc2019"; + docker manifest annotate \ + --os-version ${OS_VERSION_ltsc2022} \ + --os windows \ + --arch amd64 \ + ${target_image} "${target_image}-windows.ltsc2022"; + docker manifest push ${target_image}; + + check-release: + runs-on: ubuntu-latest + needs: + - release + steps: + - uses: actions/checkout@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Print multi-arch manifest + run: | + docker manifest inspect --verbose ${{ env.IMAGE_REPOSITORY }}:${{ github.event.release.tag_name }} > manifest_complete.json + cat manifest_complete.json | jq ".[].Descriptor.platform" | jq "(.architecture + \"_\" + .os + \"_\") + .\"os.version\"" | sort | uniq > architectures_actual.txt + + - name: Check multi-arch manifest + run: | + cat architectures_actual.txt + cat supported-architectures.txt | sort | uniq > architectures_expected.txt + # we're counting the entries only, because windows os.version numbers change too frequently + # exclude manifest entries with "unknown" architectures + diff <(echo "$(cat architectures_expected.txt | wc -l)") <(echo "$(cat architectures_actual.txt | grep -v unknown_unknown_ | wc -l)") diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 44c5c5b..0000000 --- a/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM golang:1.18-alpine3.16 AS builder -WORKDIR /go/src/github.com/testcontainers/helloworld -COPY go.mod go.sum ./ -RUN go mod download -COPY . ./ -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o /helloworld . - -FROM alpine:3.20 -COPY static /static -COPY --from=builder /helloworld /helloworld -ENTRYPOINT ["/helloworld"] diff --git a/README.md b/README.md index 5f152ec..8683750 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Testcontainers Helloworld Docker Image -This is a Docker image for use by Testcontainers' own self-test suites. It is not intended for use outside of the Testcontainers project. +This is a Docker image for use by Testcontainers' own self-test suites. It is not intended for use outside the Testcontainers project. It features a small HTTP server with the following characteristics: @@ -34,4 +34,4 @@ See [LICENSE](./LICENSE). ## Copyright -Copyright (c) 2020 Richard North and other authors. \ No newline at end of file +Copyright (c) 2020 Richard North and other authors. diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..636ad99 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,13 @@ +# Releasing Helloworld + +This is a Docker image for use by Testcontainers' own self-test suites. It is not intended for use outside the Testcontainers project. +So releases are published as Docker image to https://hub.docker.com/r/testcontainers/hellworld. + +The `.github/workflows/publish-docker-image.yml` workflow is our primary release workflow, +while preview images are published via `.github/workflows/build-docker-image.yml`. + +## Multi-Architecture support + +The workflows publishing the `testcontainers/helloworld` Docker image ensure that multiple platforms are supported. +Supported platforms are listed in the "supported-architectures.txt" file. The file is used as reference for +the checks after publishing the multi-arch image. diff --git a/architectures.txt b/architectures.txt new file mode 100644 index 0000000..017a039 --- /dev/null +++ b/architectures.txt @@ -0,0 +1,8 @@ +"386_linux_" +"amd64_linux_" +"amd64_windows_10.0.17763.5458" +"amd64_windows_10.0.20348.2322" +"arm_linux_" +"arm64_linux_" +"ppc64le_linux_" +"s390x_linux_" diff --git a/go.mod b/go.mod index ebf707e..ac02988 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/testcontainers/helloworld -go 1.18 +go 1.22 require ( - github.com/google/uuid v1.1.2 - github.com/gorilla/handlers v1.5.1 + github.com/google/uuid v1.6.0 + github.com/gorilla/handlers v1.5.2 ) -require github.com/felixge/httpsnoop v1.0.1 // indirect +require github.com/felixge/httpsnoop v1.0.3 // indirect diff --git a/go.sum b/go.sum index 2c192ef..568eb26 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= diff --git a/linux/Dockerfile b/linux/Dockerfile new file mode 100644 index 0000000..7f64635 --- /dev/null +++ b/linux/Dockerfile @@ -0,0 +1,24 @@ +# ----------- +# Build Image +# ----------- +FROM golang:1.22-alpine3.19 AS builder +ENV CGO_ENABLED=0 +#ENV GOOS=linux + +WORKDIR /app + +# Install source deps +COPY go.mod go.sum ./ +RUN go mod download + +# Copy source & build +COPY . . +RUN go build -v -ldflags '-extldflags "-static"' -o /helloworld . + +# ----------------- +# Distributed Image +# ----------------- +FROM alpine:3.20 +COPY static /static +COPY --from=builder /helloworld /helloworld +ENTRYPOINT ["/helloworld"] diff --git a/supported-architectures.txt b/supported-architectures.txt new file mode 100644 index 0000000..017a039 --- /dev/null +++ b/supported-architectures.txt @@ -0,0 +1,8 @@ +"386_linux_" +"amd64_linux_" +"amd64_windows_10.0.17763.5458" +"amd64_windows_10.0.20348.2322" +"arm_linux_" +"arm64_linux_" +"ppc64le_linux_" +"s390x_linux_" diff --git a/windows/Dockerfile b/windows/Dockerfile new file mode 100644 index 0000000..5dd860e --- /dev/null +++ b/windows/Dockerfile @@ -0,0 +1,25 @@ +ARG BASE_IMAGE + +# ----------- +# Build Image +# ----------- +FROM golang:1.22-nanoserver as builder +ENV CGO_ENABLED=0 + +WORKDIR /app + +# Install source deps +COPY go.mod go.sum ./ +RUN go mod download + +# Copy source & build +COPY . . +RUN go build -v -ldflags "-s" -o /bin/helloworld + +# ----------------- +# Distributed Image +# ----------------- +FROM ${BASE_IMAGE} +COPY static /static +COPY --from=builder /bin/helloworld /bin/helloworld +ENTRYPOINT ["/bin/helloworld"]