# Rules, macros, and build constants for building containers using Bazel ## Third party container dependencies ### `third_party_container_dep` rules The `third_party_container_dep` rule serializes third party oci containers into the Bazel build graph. It performs two functions: 1. The rule itself adds a `oci_pull` repository rule which can be used in the `WORKSPACE` file to create a reference to an `oci_image` usable in other rules that depend on images. - Ex.: a `third_party_container_dep` that adds an image name of `alpine_new` would be referenceable by other `oci_image`s via its repository name `@alpine_new` 1. The `third_party_container_dep` serves as an anchor for the container deps Gazelle extension found at `hack/build/rules/container/gazelle/language/` to create `container_push` targets. These targets serve two purposes: 1. Make the `oci_image` values available to Bazel so they can be incorporated into the build graph and usable by other rules, specifically the `kustomization` rule 1. Serve as the mechanism for how the third party images make their way into the `thirdparty` GCP artifact registry. Examples for how to create third party dependencies exist in several locations across `edge-infra`. Some good examples include: - `third_party/k8s/calico/images.bzl` - `hack/deps/images.bzl` ### Adding new third party container dependencies Adding a new dependency involves a few steps: 1. Create a new `.bzl` file in the directory where you want to keep track of the dependencies. 1. In this new file, add the `load()` statement for the `third_party_container_dep` rule: `my_images.bzl` ```python load("//hack/build/rules/container:third_party_images.bzl", "third_party_container_dep") ``` 1. Next, add a function that will wrap your `third_party_container_dep` targets into a single call: `my_images.bzl` ```python load("//hack/build/rules/container:third_party_images.bzl", "third_party_container_dep") def my_images(): """This function wraps together the third party dependencies for a specific set of needs. """ # deps will go here ``` 1. For each dependency, add a `third_party_container_dep` rule filling in the required fields: `my_images.bzl` ```python load("//hack/build/rules/container:third_party_images.bzl", "third_party_container_dep") def my_images(): """This function wraps together the third party dependencies for a specific set of needs. """ third_party_container_dep( image_name = "alpine_new", tag = "1.20", digest = "sha256:abcdef1234thisisavalidsha256", repository = "path/to/repository", registry = "index.docker.io", ) ``` The uses and requirements of each field can be found in the function documentation for the `third_party_container_dep` rule. 1. It may be useful to set Starlark constants (`ALL_CAPS_SNAKE_CASE`) variables to avoid repetition of certain values like the registry value: `my_images.bzl` ```python load("//hack/build/rules/container:third_party_images.bzl", "third_party_container_dep") SHARED_REGISTRY= "index.docker.io" def my_images(): """This function wraps together the third party dependencies for a specific set of needs. """ third_party_container_dep( image_name = "alpine_new", tag = "v1.20", digest = "sha256:abcdef1234thisisavalidsha256", repository = "path/to/alpine", registry = SHARED_REGISTRY, ) third_party_container_dep( image_name = "ubuntu_new", tag = "v2.0", digest = "sha256:abcdef1234thisisavalidsha256", repository = "path/to/ubuntu", registry = SHARED_REGISTRY, ) ``` It is also possible to import constant values from another `.bzl` file: `my_constants.bzl` ```python ANOTHER_REGISTRY = "gcr.io" ``` `my_images.bzl` ```python load("//hack/build/rules/container:third_party_images.bzl", "third_party_container_dep") load("//:my_constants.bzl", "ANOTHER_REGISTRY") SHARED_REGISTRY= "index.docker.io" def my_images(): """This function wraps together the third party dependencies for a specific set of needs. """ third_party_container_dep( image_name = "alpine_new", tag = "v1.20", digest = "sha256:abcdef1234thisisavalidsha256", repository = "path/to/alpine", registry = SHARED_REGISTRY, ) third_party_container_dep( image_name = "ubuntu_new", tag = "v2.0", digest = "sha256:abcdef1234thisisavalidsha256", repository = "path/to/ubuntu", registry = SHARED_REGISTRY, ) third_party_container_dep( image_name = "debian_new", tag = "v3.0", digest = "sha256:abcdef1234thisisavalidsha256", repository = "path/to/debian", registry = ANOTHER_REGISTRY, ) ``` 1. Once your new function is created, run `just g` and a corresponding `BUILD.bazel` file will be created next to your `my_images.bzl` file. This `BUILD.bazel` file will include a `container_push` rule for each `third_party_container_dep` rule that you added: `BUILD.bazel` ```python load("//hack/build/rules/container:index.bzl", "container_push") container_push( name = "alpine_new_container_push", digest = "@alpine_new//:digest", image = "@alpine_new//:alpine_new", image_name = "alpine_new", repository_file = "//hack/build/rules/container:thirdparty-repo", tag = "v1.20", visibility = ["//visibility:public"], ) # ... other container_push rules omitted for brevity ``` 1. Finally, `load()` your new function into `hack/deps/images.bzl` and add it to the `third_party_images()` function: ```python load("//path/to:my_images.bzl", "my_images") def third_party_images(): base_images() calico_images() # your new function here my_images() ``` 1. At this point you can query bazel for the existence of your new dependency by running `bazel query @alpine_new//...` which should return `@alpine_new//:alpine_new` signifying the repository rule was created successfully and the `oci_image` is available for use. 1. You can also build the `container_push` target by running `bazel build //path/to:alpine_new_container_push` or `bazel build //path/to/...` to build all `container_push` targets in that package. ### Migrating existing third party containers Instructions for migrating existing third party containers are found [here](./MIGRATING_EXISTING.md)