From 9ee1fb26fba038d7008270442df55b599fe0bf5d Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Wed, 6 Apr 2022 16:21:48 -0400 Subject: [PATCH] [CRT] Build docker images, rpms and debs (#1132) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - We use a multi-target dockerfile to build the control-plane image now (./control-plane/Dockerfile). It contains targets for dev, release-default and ubi - CircleCI uses the ‘dev’ target to build docker images - test-and-build.yml has been renamed to test.yml - a build.yml file has been added that CRT needs to build/sign/promote/publish artifacts - When you push to your branch, the github actions in test.yml and the CircleCI jobs will run. These are the same jobs we have had for weeks - When you push to main or /release/*** , build.yml will also run and it will build/test several artifacts that are needed for releases. These include all of our binaries, docker images and rpm/debs. - The number of PR checks goes from 17 to 30 (or close to that). - We are now building the M1 arm64 binaries and those will be published when we do our first CRT release --- .github/workflows/build.yml | 264 ++++++++++++++++++ .../{build-and-test.yml => test.yml} | 2 +- Makefile | 3 +- control-plane/Dockerfile | 152 ++++++++++ control-plane/Makefile | 6 +- 5 files changed, 421 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/build.yml rename .github/workflows/{build-and-test.yml => test.yml} (99%) create mode 100644 control-plane/Dockerfile diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..b8f6a12c9d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,264 @@ +name: build +on: + workflow_dispatch: + push: + # Sequence of patterns matched against refs/heads + branches: + # Push events on main branch + - main + # Push events to branches matching refs/heads/release/** + - "release/**" + +env: + PKG_NAME: "consul-k8s-control-plane" + +jobs: + get-go-version: + name: "Determine Go toolchain version" + runs-on: ubuntu-latest + outputs: + go-version: ${{ steps.get-go-version.outputs.go-version }} + steps: + - uses: actions/checkout@v2 + - name: Determine Go version + id: get-go-version + # We use .go-version as our source of truth for current Go + # version, because "goenv" can react to it automatically. + run: | + echo "Building with Go $(cat .go-version)" + echo "::set-output name=go-version::$(cat .go-version)" + + get-product-version: + runs-on: ubuntu-latest + outputs: + product-version: ${{ steps.get-product-version.outputs.product-version }} + steps: + - uses: actions/checkout@v2 + - name: get product version + id: get-product-version + run: | + make version + echo "::set-output name=product-version::$(make version)" + + generate-metadata-file: + needs: get-product-version + runs-on: ubuntu-latest + outputs: + filepath: ${{ steps.generate-metadata-file.outputs.filepath }} + steps: + - name: "Checkout directory" + uses: actions/checkout@v2 + - name: Generate metadata file + id: generate-metadata-file + uses: hashicorp/actions-generate-metadata@v1 + with: + version: ${{ needs.get-product-version.outputs.product-version }} + product: ${{ env.PKG_NAME }} + repositoryOwner: "hashicorp" + - uses: actions/upload-artifact@v2 + with: + name: metadata.json + path: ${{ steps.generate-metadata-file.outputs.filepath }} + + build: + needs: [get-go-version, get-product-version] + runs-on: ubuntu-latest + strategy: + matrix: + include: + # cli + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "386", component: "cli", pkg_name: "consul-k8s" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "amd64", component: "cli", pkg_name: "consul-k8s" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "386", component: "cli", pkg_name: "consul-k8s" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "cli", pkg_name: "consul-k8s" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm", component: "cli", pkg_name: "consul-k8s" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "cli", pkg_name: "consul-k8s" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "386", component: "cli", pkg_name: "consul-k8s" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "cli", pkg_name: "consul-k8s" } + # control-plane + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "386", component: "control-plane", pkg_name: "consul-k8s-control-plane" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "freebsd", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "386", component: "control-plane", pkg_name: "consul-k8s-control-plane" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm", component: "control-plane", pkg_name: "consul-k8s-control-plane" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "linux", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane" } + # solaris is only built for the control plane + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "solaris", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "386", component: "control-plane", pkg_name: "consul-k8s-control-plane" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "windows", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "cli", pkg_name: "consul-k8s" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "amd64", component: "control-plane", pkg_name: "consul-k8s-control-plane" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "cli", pkg_name: "consul-k8s" } + - {go: "${{ needs.get-go-version.outputs.go-version }}", goos: "darwin", goarch: "arm64", component: "control-plane", pkg_name: "consul-k8s-control-plane" } + fail-fast: true + + name: Go ${{ matrix.go }} ${{ matrix.goos }} ${{ matrix.goarch }} ${{ matrix.component }} build + steps: + - uses: actions/checkout@v2 + + - name: Setup go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + + - name: Build + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + CGO_ENABLED: 0 + working-directory: ${{ matrix.component }} + run: | + mkdir -p dist out + GCO_ENABLED=0 go build -o dist/${{ matrix.pkg_name }} . + zip -r -j out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip dist/ + + - name: Upload built binaries + uses: actions/upload-artifact@v2 + with: + name: ${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip + path: ${{ matrix.component}}/out/${{ matrix.pkg_name }}_${{ needs.get-product-version.outputs.product-version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip + + - name: Package rpm and deb files + if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} + uses: hashicorp/actions-packaging-linux@v1 + with: + name: consul-k8s + description: "consul-k8s provides a cli interface to first-class integrations between Consul and Kubernetes." + arch: ${{ matrix.goarch }} + version: ${{ needs.get-product-version.outputs.product-version }} + maintainer: "HashiCorp" + homepage: "https://github.com/hashicorp/consul-k8s" + license: "MPL-2.0" + binary: "${{ matrix.component }}/dist/${{ matrix.pkg_name }}" + deb_depends: "openssl" + rpm_depends: "openssl" + + - name: Set package names + if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} + run: | + echo "RPM_PACKAGE=$(basename out/*.rpm)" >> $GITHUB_ENV + echo "DEB_PACKAGE=$(basename out/*.deb)" >> $GITHUB_ENV + + - name: Test rpm package + if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} + uses: addnab/docker-run-action@v3 + with: + image: registry.access.redhat.com/ubi8/ubi:latest + options: -v ${{ github.workspace }}:/work + run: | + dnf install -qy openssl + cd /work + rpm -ivh out/${{ env.RPM_PACKAGE }} + CONSUL_K8S_VERSION="$(consul-k8s version | awk '{print $2}')" + VERSION="${{ needs.get-product-version.outputs.product-version }}" + if [ "${VERSION}" != "${CONSUL_K8S_VERSION}" ]; then + echo "Test FAILED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" + exit 1 + fi + echo "Test PASSED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" + + - name: Upload rpm package + uses: actions/upload-artifact@v2 + if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} + with: + name: ${{ env.RPM_PACKAGE }} + path: out/${{ env.RPM_PACKAGE }} + + - name: Test debian package + if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} + uses: addnab/docker-run-action@v3 + with: + image: ubuntu:latest + options: -v ${{ github.workspace }}:/work + run: | + apt update && apt install -y openssl + cd /work + apt install ./out/${{ env.DEB_PACKAGE }} + CONSUL_K8S_VERSION="$(consul-k8s version | awk '{print $2}')" + VERSION="${{ needs.get-product-version.outputs.product-version }}" + if [ "${VERSION}" != "${CONSUL_K8S_VERSION}" ]; then + echo "Test FAILED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" + exit 1 + fi + echo "Test PASSED, expected: ${VERSION}, got: ${CONSUL_K8S_VERSION}" + + - name: Upload debian packages + uses: actions/upload-artifact@v2 + if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} + with: + name: ${{ env.DEB_PACKAGE }} + path: out/${{ env.DEB_PACKAGE }} + + build-docker-default: + name: Docker ${{ matrix.arch }} default release build + needs: [get-product-version, build] + runs-on: ubuntu-latest + strategy: + matrix: + arch: ["arm", "arm64", "386", "amd64"] + env: + repo: ${{ github.event.repository.name }} + version: ${{ needs.get-product-version.outputs.product-version }} + steps: + - uses: actions/checkout@v2 + - name: Docker Build (Action) + uses: hashicorp/actions-docker-build@v1.2.1 + with: + smoke_test: | + TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" + if [ "${TEST_VERSION}" != "${version}" ]; then + echo "Test FAILED" + exit 1 + fi + echo "Test PASSED" + version: ${{ env.version }} + target: release-default + arch: ${{ matrix.arch }} + pkg_name: consul-k8s-control-plane_${{ env.version }} + bin_name: consul-k8s-control-plane + workdir: control-plane + tags: | + docker.io/hashicorp/${{ env.repo }}-control-plane:${{ env.version }} + 986891699432.dkr.ecr.us-east-1.amazonaws.com/hashicorp/${{ env.repo }}-control-plane:${{ env.version }} + dev_tags: | + hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }}-dev + docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }}-${{ github.sha }} + +# Disabling UBI build for now so that we do not inadvertently push a ubi image that we have not tested. + # build-docker-alternate: + # name: Docker ${{ matrix.arch }} alternate release build + # needs: [get-product-version, build] + # runs-on: ubuntu-latest + # strategy: + # matrix: + # arch: ["amd64"] + # env: + # repo: ${{ github.event.repository.name }} + # version: ${{ needs.get-product-version.outputs.product-version }} + # + # steps: + # - uses: actions/checkout@v2 + # - name: Copy LICENSE.md + # run: + # cp LICENSE.md ./control-plane + # - uses: hashicorp/actions-docker-build@v1.2.1 + # with: + # smoke_test: | + # TEST_VERSION="$(docker run "${IMAGE_NAME}" consul-k8s-control-plane version | awk '{print $2}')" + # if [ "${TEST_VERSION}" != "${version}" ]; then + # echo "Test FAILED" + # exit 1 + # fi + # echo "Test PASSED" + # version: ${{ env.version }} + # target: ubi + # arch: ${{ matrix.arch }} + # pkg_name: consul-k8s-control-plane_${{ env.version }} + # bin_name: consul-k8s-control-plane + # workdir: control-plane + # tags: | + # registry.connect.redhat.com/hashicorp/consul-k8s-control-plane:${{env.version}}-ubi + # dev_tags: | + # hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }}-ubi-dev + # docker.io/hashicorppreview/${{ env.repo }}-control-plane:${{ env.version }}-ubi-${{ github.sha }} + # diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/test.yml similarity index 99% rename from .github/workflows/build-and-test.yml rename to .github/workflows/test.yml index 1b9e8fb019..d58b35326b 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: test-and-build +name: test on: push: diff --git a/Makefile b/Makefile index 9ef0c0a0cd..93e0db1ab8 100644 --- a/Makefile +++ b/Makefile @@ -20,10 +20,11 @@ control-plane-dev: ## Build consul-k8s-control-plane binary. control-plane-dev-docker: ## Build consul-k8s-control-plane dev Docker image. @$(SHELL) $(CURDIR)/control-plane/build-support/scripts/build-local.sh -o linux -a amd64 @DOCKER_DEFAULT_PLATFORM=linux/amd64 docker build -t '$(DEV_IMAGE)' \ + --target=dev \ --build-arg 'GIT_COMMIT=$(GIT_COMMIT)' \ --build-arg 'GIT_DIRTY=$(GIT_DIRTY)' \ --build-arg 'GIT_DESCRIBE=$(GIT_DESCRIBE)' \ - -f $(CURDIR)/control-plane/build-support/docker/Dev.dockerfile $(CURDIR)/control-plane + -f $(CURDIR)/control-plane/Dockerfile $(CURDIR)/control-plane control-plane-test: ## Run go test for the control plane. cd control-plane; go test ./... diff --git a/control-plane/Dockerfile b/control-plane/Dockerfile new file mode 100644 index 0000000000..4ebc2f3d9c --- /dev/null +++ b/control-plane/Dockerfile @@ -0,0 +1,152 @@ +# This Dockerfile contains multiple targets. +# Use 'docker build --target= .' to build one. +# +# Every target has a BIN_NAME argument that must be provided via --build-arg=BIN_NAME= +# when building. + + +# =================================== +# +# Non-release images. +# +# =================================== + +# dev copies the binary from a local build +# ----------------------------------- +# BIN_NAME is a requirement in the hashicorp docker github action + +FROM alpine:3.15 AS dev + +# NAME and VERSION are the name of the software in releases.hashicorp.com +# and the version to download. Example: NAME=consul VERSION=1.2.3. +ARG BIN_NAME=consul-k8s-control-plane +ARG VERSION + +LABEL name=${BIN_NAME} \ + maintainer="Team Consul Kubernetes " \ + vendor="HashiCorp" \ + version=${VERSION} \ + release=${VERSION} \ + summary="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." \ + description="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." + +# Set ARGs as ENV so that they can be used in ENTRYPOINT/CMD +ENV BIN_NAME=${BIN_NAME} +ENV VERSION=${VERSION} + +RUN apk add --no-cache ca-certificates curl gnupg libcap openssl su-exec iputils libc6-compat iptables + +# Create a non-root user to run the software. +RUN addgroup ${BIN_NAME} && \ + adduser -S -G ${BIN_NAME} 100 + +COPY pkg/bin/linux_amd64/${BIN_NAME} /bin + +USER 100 +CMD /bin/${BIN_NAME} + + +# =================================== +# +# Release images. +# +# =================================== + + +# default release image +# ----------------------------------- +# This Dockerfile creates a production release image for the project. This +# downloads the release from releases.hashicorp.com and therefore requires that +# the release is published before building the Docker image. +# +# We don't rebuild the software because we want the exact checksums and +# binary signatures to match the software and our builds aren't fully +# reproducible currently. +FROM alpine:3.15 AS release-default + +# NAME and VERSION are the name of the software in releases.hashicorp.com +# and the version to download. Example: NAME=consul VERSION=1.2.3. +ARG BIN_NAME=consul-k8s-control-plane +ARG VERSION + +LABEL name=${BIN_NAME} \ + maintainer="Team Consul Kubernetes " \ + vendor="HashiCorp" \ + version=${VERSION} \ + release=${VERSION} \ + summary="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." \ + description="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." + +# Set ARGs as ENV so that they can be used in ENTRYPOINT/CMD +ENV BIN_NAME=${BIN_NAME} +ENV VERSION=${VERSION} + +RUN apk add --no-cache ca-certificates curl gnupg libcap openssl su-exec iputils libc6-compat iptables + +# TARGETOS and TARGETARCH are set automatically when --platform is provided. +ARG TARGETOS TARGETARCH + +# Create a non-root user to run the software. +RUN addgroup ${BIN_NAME} && \ + adduser -S -G ${BIN_NAME} 100 +COPY dist/${TARGETOS}/${TARGETARCH}/${BIN_NAME} /bin/ + +USER 100 +CMD /bin/${BIN_NAME} + +# ----------------------------------- +# Dockerfile target for consul-k8s with UBI as its base image. Used for running on +# OpenShift. +# +# This Dockerfile creates a production release image for the project. This +# downloads the release from releases.hashicorp.com and therefore requires that +# the release is published before building the Docker image. +# +# We don't rebuild the software because we want the exact checksums and +# binary signatures to match the software and our builds aren't fully +# reproducible currently. +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.5 as ubi + +# NAME and VERSION are the name of the software in releases.hashicorp.com +# and the version to download. Example: NAME=consul VERSION=1.2.3. +ARG BIN_NAME +ARG VERSION + +LABEL name=${BIN_NAME} \ + maintainer="Team Consul Kubernetes " \ + vendor="HashiCorp" \ + version=${VERSION} \ + release=${VERSION} \ + summary="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." \ + description="consul-k8s-control-plane provides first-class integrations between Consul and Kubernetes." + +# Set ARGs as ENV so that they can be used in ENTRYPOINT/CMD +ENV NAME=${BIN_NAME} +ENV VERSION=${VERSION} + +# TARGETOS and TARGETARCH are set automatically when --platform is provided. +ARG TARGETOS TARGETARCH + +# Copy license for Red Hat certification. +COPY LICENSE.md /licenses/mozilla.txt + +RUN microdnf install -y ca-certificates gnupg libcap openssl shadow-utils iptables + +# Create a non-root user to run the software. On OpenShift, this +# will not matter since the container is run as a random user and group +# but this is kept for consistency with our other images. +RUN groupadd --gid 1000 ${BIN_NAME} && \ + adduser --uid 100 --system -g ${BIN_NAME} ${BIN_NAME} && \ + usermod -a -G root ${BIN_NAME} + +COPY dist/${TARGETOS}/${TARGETARCH}/${BIN_NAME} /bin/ + +USER 100 +CMD /bin/${BIN_NAME} + +# =================================== +# +# Set default target to 'dev'. +# +# =================================== +FROM dev diff --git a/control-plane/Makefile b/control-plane/Makefile index eb174b3c1c..85daf7a434 100644 --- a/control-plane/Makefile +++ b/control-plane/Makefile @@ -31,15 +31,13 @@ ci.dev-tree: # In CircleCI, the linux binary will be attached from a previous step at pkg/bin/linux_amd64/. This make target # should only run in CI and not locally. ci.dev-docker: - @echo "Pulling consul-k8s container image - $(CONSUL_K8S_IMAGE_VERSION)" - @docker pull hashicorp/consul-k8s:$(CONSUL_K8S_IMAGE_VERSION) >/dev/null #todo change this back after pulling it the first time since the dockerfile is FROM this image @echo "Building consul-k8s Development container - $(CI_DEV_DOCKER_IMAGE_NAME)" @docker build -t '$(CI_DEV_DOCKER_NAMESPACE)/$(CI_DEV_DOCKER_IMAGE_NAME):$(GIT_COMMIT)' \ - --build-arg CONSUL_K8S_IMAGE_VERSION=$(CONSUL_K8S_IMAGE_VERSION) \ + --target=dev \ --label COMMIT_SHA=$(CIRCLE_SHA1) \ --label PULL_REQUEST=$(CIRCLE_PULL_REQUEST) \ --label CIRCLE_BUILD_URL=$(CIRCLE_BUILD_URL) \ - $(CI_DEV_DOCKER_WORKDIR) -f $(CURDIR)/build-support/docker/Dev.dockerfile + $(CI_DEV_DOCKER_WORKDIR) -f $(CURDIR)/Dockerfile @echo $(DOCKER_PASS) | docker login -u="$(DOCKER_USER)" --password-stdin @echo "Pushing dev image to: https://cloud.docker.com/u/$(CI_DEV_DOCKER_NAMESPACE)/repository/docker/$(CI_DEV_DOCKER_NAMESPACE)/$(CI_DEV_DOCKER_IMAGE_NAME)" @docker push $(CI_DEV_DOCKER_NAMESPACE)/$(CI_DEV_DOCKER_IMAGE_NAME):$(GIT_COMMIT)