diff --git a/template/.actionlint.yaml b/template/.actionlint.yaml new file mode 100644 index 00000000..aa2d2b97 --- /dev/null +++ b/template/.actionlint.yaml @@ -0,0 +1,6 @@ +--- +self-hosted-runner: + # BuildJet machines we are using + labels: + - buildjet-2vcpu-ubuntu-2204-arm + - buildjet-4vcpu-ubuntu-2204-arm diff --git a/template/.github/workflows/build.yml.j2 b/template/.github/workflows/build.yml.j2 index 7adb3fe7..9e979128 100644 --- a/template/.github/workflows/build.yml.j2 +++ b/template/.github/workflows/build.yml.j2 @@ -220,7 +220,7 @@ jobs: - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: submodules: recursive - - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # tag=v5.1.0 + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: '3.12' - name: Install jinja2-cli @@ -298,11 +298,14 @@ jobs: run: echo All tests have passed! package_and_publish: - name: Package Charts, Build Docker Image and publish them + name: Package Charts, Build Docker Image and publish them - ${{ matrix.runner }} needs: - tests_passed - select_helm_repo - runs-on: ubuntu-latest + strategy: + matrix: + runner: ["ubuntu-latest", "buildjet-2vcpu-ubuntu-2204-arm"] + runs-on: ${{ matrix.runner }} permissions: id-token: write env: @@ -324,7 +327,7 @@ jobs: - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: submodules: recursive - - uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # tag=v26 + - uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26 - uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a with: toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} @@ -340,15 +343,25 @@ jobs: if: ${{ github.event_name == 'pull_request' }} run: cargo set-version --offline --workspace 0.0.0-pr${{ github.event.pull_request.number }} - # Recreate charts and publish charts and docker image. The "-e" is needed as we want to override the - # default value in the makefile if called from this action, but not otherwise (i.e. when called locally). - # This is needed for the HELM_REPO variable. + # Recreate charts and publish charts and docker image. The "-e" is needed as we want to override the + # default value in the makefile if called from this action, but not otherwise (i.e. when called locally). + # This is needed for the HELM_REPO variable. - name: Install cosign - uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # tag=v3.5.0 + uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0 - name: Install syft uses: anchore/sbom-action/download-syft@7ccf588e3cf3cc2611714c2eeae48550fbc17552 # v0.15.11 - name: Build Docker image and Helm chart - run: make -e build-ci + run: | + # Installing helm on BuildJet only + if [ "$(arch)" = "aarch64" ]; then + curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null + sudo apt-get -y install apt-transport-https --yes + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list + sudo apt-get -y update + sudo apt-get -y install helm + fi + + make -e build - name: Publish Docker image and Helm chart if: ${{ !github.event.pull_request.head.repo.fork }} run: make -e publish @@ -358,6 +371,43 @@ jobs: if: ${{ !github.event.pull_request.head.repo.fork }} run: echo "IMAGE_TAG=$(make -e print-docker-tag)" >> $GITHUB_OUTPUT + create_manifest_list: + name: Build and publish manifest list + needs: + - package_and_publish + runs-on: ubuntu-latest + permissions: + id-token: write + env: + NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} + OCI_REGISTRY_SDP_PASSWORD: ${{ secrets.HARBOR_ROBOT_SDP_GITHUB_ACTION_BUILD_SECRET }} + OCI_REGISTRY_SDP_USERNAME: "robot$sdp+github-action-build" + OCI_REGISTRY_SDP_CHARTS_PASSWORD: ${{ secrets.HARBOR_ROBOT_SDP_CHARTS_GITHUB_ACTION_BUILD_SECRET }} + OCI_REGISTRY_SDP_CHARTS_USERNAME: "robot$sdp-charts+github-action-build" + steps: + - name: Install cosign + uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0 + - name: Checkout + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + submodules: recursive + # This step checks if the current run was triggered by a push to a pr (or a pr being created). + # If this is the case it changes the version of this project in all Cargo.toml files to include the suffix + # "-pr" so that the published artifacts can be linked to this PR. + - uses: stackabletech/cargo-install-action@main + with: + crate: cargo-edit + bin: cargo-set-version + - name: Update version if PR + if: ${{ github.event_name == 'pull_request' }} + run: cargo set-version --offline --workspace 0.0.0-pr${{ github.event.pull_request.number }} + - name: Build manifest list + run: | + # Creating manifest list + make -e docker-manifest-list-build + # Pushing and signing manifest list + make -e docker-manifest-list-publish + openshift_preflight: name: Run the OpenShift Preflight check on the published images if: ${{ !github.event.pull_request.head.repo.fork }} @@ -376,4 +426,4 @@ jobs: ARCH_FOR_PREFLIGHT="$(arch | sed -e 's#x86_64#amd64#' | sed -e 's#aarch64#arm64#')" ./preflight-linux-amd64 check container "$IMAGE_TAG" --platform "${ARCH_FOR_PREFLIGHT}" > preflight.out - name: "Passed?" - run: '[ "$(./preflight-linux-amd64 check container "$IMAGE_TAG" --platform "${ARCH_FOR_PREFLIGHT}" | jq -r .passed)" == true ]' + run: '[ "$(cat preflight.out | jq -r .passed)" == true ]' diff --git a/template/.gitignore.j2 b/template/.gitignore.j2 index 8bd12182..2dbc7ab0 100644 --- a/template/.gitignore.j2 +++ b/template/.gitignore.j2 @@ -17,3 +17,5 @@ tilt_options.json .direnv/ .direnvrc .envrc + +.DS_Store diff --git a/template/Makefile.j2 b/template/Makefile.j2 index ce865708..1ea8764f 100644 --- a/template/Makefile.j2 +++ b/template/Makefile.j2 @@ -12,6 +12,7 @@ TAG := $(shell git rev-parse --short HEAD) OPERATOR_NAME := {[ operator.name }] VERSION := $(shell cargo metadata --format-version 1 | jq -r '.packages[] | select(.name=="stackable-${OPERATOR_NAME}") | .version') +ARCH := $(shell arch | sed -e 's#x86_64#amd64#' | sed -e 's#aarch64#arm64#') DOCKER_REPO := docker.stackable.tech ORGANIZATION := stackable @@ -30,17 +31,17 @@ render-readme: ## Docker related targets docker-build: - docker build --force-rm --build-arg VERSION=${VERSION} -t "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}" -f docker/Dockerfile . - docker tag "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}" "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}" + docker build --force-rm --build-arg VERSION=${VERSION} -t "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}-${ARCH}" -f docker/Dockerfile . + docker tag "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}-${ARCH}" "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-${ARCH}" docker-publish: # Push to Nexus echo "${NEXUS_PASSWORD}" | docker login --username github --password-stdin "${DOCKER_REPO}" DOCKER_OUTPUT=$$(docker push --all-tags "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}");\ # Obtain the digest of the pushed image from the output of `docker push`, because signing by tag is deprecated and will be removed from cosign in the future\ - REPO_DIGEST_OF_IMAGE=$$(echo "$$DOCKER_OUTPUT" | awk '/^${VERSION}: digest: sha256:[0-9a-f]{64} size: [0-9]+$$/ { print $$3 }');\ + REPO_DIGEST_OF_IMAGE=$$(echo "$$DOCKER_OUTPUT" | awk '/^${VERSION}-${ARCH}: digest: sha256:[0-9a-f]{64} size: [0-9]+$$/ { print $$3 }');\ if [ -z "$$REPO_DIGEST_OF_IMAGE" ]; then\ - echo 'Could not find repo digest for container image: ${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}';\ + echo 'Could not find repo digest for container image: ${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}-${ARCH}';\ exit 1;\ fi;\ # This generates a signature and publishes it to the registry, next to the image\ @@ -51,8 +52,8 @@ docker-publish: # Determine the PURL for the container image\ PURL="pkg:docker/${ORGANIZATION}/${OPERATOR_NAME}@$$REPO_DIGEST_OF_IMAGE?repository_url=${DOCKER_REPO}";\ # Get metadata from the image\ - IMAGE_DESCRIPTION=$$(docker inspect --format='{{.Config.Labels.description}}' "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}");\ - IMAGE_NAME=$$(docker inspect --format='{{.Config.Labels.name}}' "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}");\ + IMAGE_DESCRIPTION=$$(docker inspect --format='{{.Config.Labels.description}}' "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}-${ARCH}");\ + IMAGE_NAME=$$(docker inspect --format='{{.Config.Labels.name}}' "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}-${ARCH}");\ # Merge the SBOM with the metadata for the operator\ jq -s '{"metadata":{"component":{"description":"'"$$IMAGE_NAME. $$IMAGE_DESCRIPTION"'","supplier":{"name":"Stackable GmbH","url":["https://stackable.tech/"]},"author":"Stackable GmbH","purl":"'"$$PURL"'","publisher":"Stackable GmbH"}}} * .[0]' sbom.json > sbom.merged.json;\ # Attest the SBOM to the image\ @@ -63,9 +64,9 @@ docker-publish: docker login --username '${value OCI_REGISTRY_SDP_USERNAME}' --password '${OCI_REGISTRY_SDP_PASSWORD}' '${OCI_REGISTRY_HOSTNAME}' DOCKER_OUTPUT=$$(docker push --all-tags '${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}');\ # Obtain the digest of the pushed image from the output of `docker push`, because signing by tag is deprecated and will be removed from cosign in the future\ - REPO_DIGEST_OF_IMAGE=$$(echo "$$DOCKER_OUTPUT" | awk '/^${VERSION}: digest: sha256:[0-9a-f]{64} size: [0-9]+$$/ { print $$3 }');\ + REPO_DIGEST_OF_IMAGE=$$(echo "$$DOCKER_OUTPUT" | awk '/^${VERSION}-${ARCH}: digest: sha256:[0-9a-f]{64} size: [0-9]+$$/ { print $$3 }');\ if [ -z "$$REPO_DIGEST_OF_IMAGE" ]; then\ - echo 'Could not find repo digest for container image: ${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}';\ + echo 'Could not find repo digest for container image: ${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-${ARCH}';\ exit 1;\ fi;\ # This generates a signature and publishes it to the registry, next to the image\ @@ -76,13 +77,39 @@ docker-publish: # Determine the PURL for the container image\ PURL="pkg:docker/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}@$$REPO_DIGEST_OF_IMAGE?repository_url=${OCI_REGISTRY_HOSTNAME}";\ # Get metadata from the image\ - IMAGE_DESCRIPTION=$$(docker inspect --format='{{.Config.Labels.description}}' "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}");\ - IMAGE_NAME=$$(docker inspect --format='{{.Config.Labels.name}}' "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}");\ + IMAGE_DESCRIPTION=$$(docker inspect --format='{{.Config.Labels.description}}' "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-${ARCH}");\ + IMAGE_NAME=$$(docker inspect --format='{{.Config.Labels.name}}' "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-${ARCH}");\ # Merge the SBOM with the metadata for the operator\ jq -s '{"metadata":{"component":{"description":"'"$$IMAGE_NAME. $$IMAGE_DESCRIPTION"'","supplier":{"name":"Stackable GmbH","url":["https://stackable.tech/"]},"author":"Stackable GmbH","purl":"'"$$PURL"'","publisher":"Stackable GmbH"}}} * .[0]' sbom.json > sbom.merged.json;\ # Attest the SBOM to the image\ cosign attest -y --predicate sbom.merged.json --type cyclonedx "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}@$$REPO_DIGEST_OF_IMAGE" +# This assumes "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}-amd64 and "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}-arm64 are build and pushed +docker-manifest-list-build: + docker manifest create "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}" --amend "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}-amd64" --amend "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}-arm64" + docker manifest create "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}" --amend "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-amd64" --amend "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-arm64" + +docker-manifest-list-publish: + # Push to Nexus + echo "${NEXUS_PASSWORD}" | docker login --username github --password-stdin "${DOCKER_REPO}" + # `docker manifest push` directly returns the digest of the manifest list + # As it is an experimental feature, this might change in the future + # Further reading: https://docs.docker.com/reference/cli/docker/manifest/push/ + DIGEST_NEXUS=$$(docker manifest push "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}");\ + # Refer to image via its digest (oci.stackable.tech/sdp/airflow@sha256:0a1b2c...)\ + # This generates a signature and publishes it to the registry, next to the image\ + # Uses the keyless signing flow with Github Actions as identity provider\ + cosign sign -y "${DOCKER_REPO}/${ORGANIZATION}/${OPERATOR_NAME}:${VERSION}@$$DIGEST_NEXUS" + + # Push to Harbor + # We need to use "value" here to prevent the variable from being recursively expanded by make (username contains a dollar sign, since it's a Harbor bot) + docker login --username '${value OCI_REGISTRY_SDP_USERNAME}' --password '${OCI_REGISTRY_SDP_PASSWORD}' '${OCI_REGISTRY_HOSTNAME}' + DIGEST_HARBOR=$$(docker manifest push "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}");\ + # Refer to image via its digest (oci.stackable.tech/sdp/airflow@sha256:0a1b2c...);\ + # This generates a signature and publishes it to the registry, next to the image\ + # Uses the keyless signing flow with Github Actions as identity provider\ + cosign sign -y "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}@$$DIGEST_HARBOR" + # TODO remove if not used/needed docker: docker-build docker-publish