diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 39649511..d565d4fd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -141,6 +141,13 @@ jobs: ], ) EOF + + # the /examples/dockerfile uses buildx to build Dockerfile images which needs to use a docker-container + # builder to work, which does not exist by default. Create one here. + - name: Setup buildx + if: ${{ matrix.os == 'ubuntu-latest' }} + run: bazel run examples/dockerfile:buildx -- create --name container --driver=docker-container + - name: bazel test //... working-directory: ${{ matrix.folder }} env: diff --git a/WORKSPACE b/WORKSPACE index 1a5a219a..2776bdc6 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -80,3 +80,8 @@ new_local_repository( load(":fetch.bzl", "fetch_images") fetch_images() + +### Fetch buildx +load("//examples/dockerfile:buildx.bzl", "fetch_buildx") + +fetch_buildx() diff --git a/WORKSPACE.bzlmod b/WORKSPACE.bzlmod index 5ccd045b..693a2e87 100644 --- a/WORKSPACE.bzlmod +++ b/WORKSPACE.bzlmod @@ -19,3 +19,8 @@ fetch_images() load("//cosign:repositories.bzl", "cosign_register_toolchains") cosign_register_toolchains(name = "oci_cosign") + +### Fetch buildx +load("//examples/dockerfile:buildx.bzl", "fetch_buildx") + +fetch_buildx() diff --git a/e2e/convert_docker_tarball/.gitignore b/e2e/convert_docker_tarball/.gitignore deleted file mode 100644 index 529b7102..00000000 --- a/e2e/convert_docker_tarball/.gitignore +++ /dev/null @@ -1 +0,0 @@ -image.tar \ No newline at end of file diff --git a/e2e/convert_docker_tarball/BUILD.bazel b/e2e/convert_docker_tarball/BUILD.bazel deleted file mode 100644 index a98981b1..00000000 --- a/e2e/convert_docker_tarball/BUILD.bazel +++ /dev/null @@ -1,45 +0,0 @@ -load("@aspect_bazel_lib//lib:run_binary.bzl", "run_binary") -load("@container_structure_test//:defs.bzl", "container_structure_test") -load("@rules_oci//oci:defs.bzl", "oci_image", "oci_tarball") - -sh_binary( - name = "convert", - srcs = ["convert.bash"], - data = [ - "@oci_crane_toolchains//:current_toolchain", - ], -) - -# Before building this example, you'll need to run ./create_base_image.bash to produce an -# image.tar file. It's large so we .gitignore it. -run_binary( - name = "base", - srcs = ["image.tar"], - args = [ - "$@", - "$(location :image.tar)", - "$(CRANE_BIN)", - ], - out_dirs = ["oci"], - tool = ":convert", - toolchains = [ - "@oci_crane_toolchains//:current_toolchain", - ], -) - -oci_image( - name = "image", - base = ":base", -) - -oci_tarball( - name = "tar", - image = ":image", - repo_tags = [], -) - -container_structure_test( - name = "test", - configs = ["test.yaml"], - image = ":image", -) diff --git a/e2e/convert_docker_tarball/README.md b/e2e/convert_docker_tarball/README.md deleted file mode 100644 index 92a25fe4..00000000 --- a/e2e/convert_docker_tarball/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Convert a docker tarball as a base image - -In some cases, your legacy setup doesn't fetch a base image from a remote registry, instead you've produced your base image in a script and check or fetch the tarball. - -To generate the `image.tar` file, first run `create_base_image.bash`. Then build the example normally. diff --git a/e2e/convert_docker_tarball/WORKSPACE b/e2e/convert_docker_tarball/WORKSPACE deleted file mode 100644 index f7158640..00000000 --- a/e2e/convert_docker_tarball/WORKSPACE +++ /dev/null @@ -1,27 +0,0 @@ -workspace(name = "tarball") - -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - -local_repository( - name = "rules_oci", - path = "../../", -) - -load("@rules_oci//oci:dependencies.bzl", "rules_oci_dependencies") - -rules_oci_dependencies() - -load("@rules_oci//oci:repositories.bzl", "oci_register_toolchains") - -oci_register_toolchains(name = "oci") - -http_archive( - name = "container_structure_test", - sha256 = "4fd1e0d4974fb95e06d0e94e6ceaae126382bf958524062db4e582232590b863", - strip_prefix = "container-structure-test-1.16.1", - urls = ["https://github.com/GoogleContainerTools/container-structure-test/archive/v1.16.1.zip"], -) - -load("@container_structure_test//:repositories.bzl", "container_structure_test_register_toolchain") - -container_structure_test_register_toolchain(name = "st") diff --git a/e2e/convert_docker_tarball/convert.bash b/e2e/convert_docker_tarball/convert.bash deleted file mode 100755 index e0b0599c..00000000 --- a/e2e/convert_docker_tarball/convert.bash +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -# Most rules_oci users will use oci_pull to fetch base layers from a remote registry. -# However, you might build docker-format tarballs and uploaded them to Artifactory for example. -# rules_oci expects an OCI format base image, so these need to be converted. -# -# This just requires push'ing the tarball into a locally-running registry which -# understands both formats, then pull'ing back out in the oci format. -# -# Note, this is suboptimal because Bazel will still have to execute an action that has the entire -# base image as an input. Large inputs cause network delays with remote execution. - -set -o pipefail -o errexit -o nounset - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -RUNFILES="$SCRIPT_DIR/$(basename $0).runfiles" - -readonly OUTPUT="${1}" -readonly TARBALL="${2}" -readonly CRANE="${RUNFILES}/${3#"external/"}" - -# Launch a registry instance at a random port -output=$(mktemp) -$CRANE registry serve --address=localhost:0 >> $output 2>&1 & -timeout=$((SECONDS+10)) -while [ "${SECONDS}" -lt "${timeout}" ]; do - port="$(cat $output | sed -nr 's/.+serving on port ([0-9]+)/\1/p')" - if [ -n "${port}" ]; then - break - fi -done -REGISTRY="localhost:$port" -echo "Registry is running at ${REGISTRY}" -readonly REPOSITORY="${REGISTRY}/local" - - -REF=$("${CRANE}" push "${TARBALL}" "${REPOSITORY}") - -"${CRANE}" pull "$REF" "${OUTPUT}" --format=oci \ No newline at end of file diff --git a/e2e/convert_docker_tarball/create_base_image.bash b/e2e/convert_docker_tarball/create_base_image.bash deleted file mode 100755 index 9589c1e7..00000000 --- a/e2e/convert_docker_tarball/create_base_image.bash +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -cd "$(dirname "${BASH_SOURCE[0]}")" - -# Export image -docker build . -t temp --no-cache -docker save temp -o image.tar - diff --git a/examples/dockerfile/BUILD.bazel b/examples/dockerfile/BUILD.bazel new file mode 100644 index 00000000..a5351f32 --- /dev/null +++ b/examples/dockerfile/BUILD.bazel @@ -0,0 +1,50 @@ +load("@aspect_bazel_lib//lib:run_binary.bzl", "run_binary") +load("@bazel_skylib//rules:native_binary.bzl", "native_binary") +load("@container_structure_test//:defs.bzl", "container_structure_test") +load("@rules_oci//oci:defs.bzl", "oci_image", "oci_tarball") + +native_binary( + name = "buildx", + src = select({ + "@bazel_tools//src/conditions:linux_x86_64": "@buildx_linux_amd64//file", + "@bazel_tools//src/conditions:darwin_arm64": "@buildx_darwin_arm64//file", + "@bazel_tools//src/conditions:darwin_x86_64": "@buildx_darwin_amd64//file", + }), + out = "buildx", +) + +# docker buildx create --name container --driver=docker-container +run_binary( + name = "base", + srcs = ["Dockerfile"] + glob(["src/*"]), + args = [ + "build", + "./examples/dockerfile", + "--builder", + "container", + "--output=type=oci,tar=false,dest=$@", + ], + execution_requirements = {"local": "1"}, + out_dirs = ["base"], + target_compatible_with = [ + "@platforms//os:linux", + ], + tool = ":buildx", +) + +oci_image( + name = "image", + base = ":base", +) + +oci_tarball( + name = "tar", + image = ":image", + repo_tags = [], +) + +container_structure_test( + name = "test", + configs = ["test.yaml"], + image = ":image", +) diff --git a/e2e/convert_docker_tarball/Dockerfile b/examples/dockerfile/Dockerfile similarity index 72% rename from e2e/convert_docker_tarball/Dockerfile rename to examples/dockerfile/Dockerfile index 61cbbd6e..a4a7c862 100644 --- a/e2e/convert_docker_tarball/Dockerfile +++ b/examples/dockerfile/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:18.04 +FROM python:3.11.9-bullseye ARG DEBIAN_FRONTEND=noninteractive @@ -10,3 +10,9 @@ RUN apt-get -y update \ && apt-get -y install jq \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +RUN pip install cowsay + +COPY src /app + +CMD ["/app/say.py"] \ No newline at end of file diff --git a/examples/dockerfile/README.md b/examples/dockerfile/README.md new file mode 100644 index 00000000..c2bb39a0 --- /dev/null +++ b/examples/dockerfile/README.md @@ -0,0 +1,17 @@ +# Dockerfile + rules_oci + +STOP before committing this atrocity. Here's some good reasons why you should not do what we have done here. + +- Dockerfiles are fundamentally non-reproducible +- Reproducible builds are important for Bazel, Dockerfiles will lead to poor cache hits. +- `RUN` instruction is a perfect foot-gun for non-reprocubile builds, a simple command `RUN apt-get install curl` is non-hermetic by default. +- Building the same Dockerfile one month apart will yield different results. +- `FROM python:3.11.9-bullseye` is non-producible. + +# Resources + +https://reproducible-builds.org/ +https://github.com/bazel-contrib/rules_oci/issues/35#issuecomment-1285954483 +https://github.com/bazel-contrib/rules_oci/blob/main/docs/compare_dockerfile.md +https://github.com/moby/moby/issues/43124 +https://medium.com/nttlabs/bit-for-bit-reproducible-builds-with-dockerfile-7cc2b9faed9f diff --git a/examples/dockerfile/buildx.bzl b/examples/dockerfile/buildx.bzl new file mode 100644 index 00000000..12ae7f43 --- /dev/null +++ b/examples/dockerfile/buildx.bzl @@ -0,0 +1,31 @@ +"repos for buildx" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") + +def fetch_buildx(): + http_file( + name = "buildx_linux_amd64", + urls = [ + "https://github.com/docker/buildx/releases/download/v0.14.0/buildx-v0.14.0.linux-amd64", + ], + integrity = "sha256-Mvjxfso1vy7+bA5H9A5Gkqh280UxtCHvyYR5mltBIm4=", + executable = True, + ) + + http_file( + name = "buildx_darwin_arm64", + urls = [ + "https://github.com/docker/buildx/releases/download/v0.14.0/buildx-v0.14.0.darwin-arm64", + ], + integrity = "sha256-3BdvI2ZgnMITKubwi7IZOjL5/ZNUv9Agz3+juNt0hA0=", + executable = True, + ) + + http_file( + name = "buildx_darwin_amd64", + urls = [ + "https://github.com/docker/buildx/releases/download/v0.14.0/buildx-v0.14.0.darwin-amd64", + ], + integrity = "sha256-J6rZfENSvCzFBHDgnA8Oqq2FDXR+M9CTejhhg9DruPU=", + executable = True, + ) diff --git a/examples/dockerfile/src/say.py b/examples/dockerfile/src/say.py new file mode 100644 index 00000000..6f19226d --- /dev/null +++ b/examples/dockerfile/src/say.py @@ -0,0 +1,3 @@ +import cowsay + +cowsay.cow('moo!') \ No newline at end of file diff --git a/e2e/convert_docker_tarball/test.yaml b/examples/dockerfile/test.yaml similarity index 63% rename from e2e/convert_docker_tarball/test.yaml rename to examples/dockerfile/test.yaml index cc3112c5..79fd9d95 100644 --- a/e2e/convert_docker_tarball/test.yaml +++ b/examples/dockerfile/test.yaml @@ -9,13 +9,20 @@ metadataTest: - key: "LANGUAGE" value: "C.UTF-8" entrypoint: [] - cmd: ["/bin/bash"] + cmd: ["/app/say.py"] commandTests: - name: "jq should be installed" command: "jq" - expectedError: ["jq - commandline JSON processor"] - exitCode: 2 + args: ["--version"] + expectedOutput: ["jq\\-1\\.6"] + exitCode: 0 + + - name: "should say moo" + command: "python" + args: ["/app/say.py"] + expectedOutput: ["moo!"] + exitCode: 0 fileExistenceTests: - name: "should not remove /var/lib/apt/lists"