rules_kubectl
Bazel rules for a hermetic kubectl.
The module does two things and nothing else:
- it fetches
kubectlfor the host and exposes it as@kubectl//:kubectlforbazel run, and - it defines
@rules_kubectl//kubectl:toolchain_typeso that any rule set needingkubectlin an action can share one binary instead of fetching its own.
There are no kubectl_apply style rules here. Opinions about kubeconfigs,
contexts and namespaces belong in the rule sets built on top of the toolchain.
Setup
bazel_dep(name = "rules_kubectl", version = "0.1.0")
kubectl = use_extension("@rules_kubectl//kubectl:extensions.bzl", "kubectl")
kubectl.toolchain()
use_repo(kubectl, "kubectl")
rules_kubectl registers the toolchains itself, so no register_toolchains
call is needed.
The bazel run launcher in front of kubectl is a small Go program that finds
the binary through runfiles, so rules_go and a Go SDK are fetched the first
time it is built.
Running kubectl
bazel run @kubectl -- get pods --namespace my-namespace
bazel run @kubectl -- apply -f ./deploy/configmap.yaml
bazel run @kubectl -- kustomize ./deploy/overlays/dev
bazel run passes your environment through, so KUBECONFIG, ~/.kube/config
and the usual --context / --namespace flags behave exactly as they do for
a locally installed kubectl. The @kubectl//:kubectl target also starts
kubectl from the directory you ran bazel in rather than from the runfiles
tree, which is why the relative paths above work.
Pinning a version
rules_kubectl ships checksums for a single default version. Clusters more
than one minor version away from it should pin a matching release:
kubectl.toolchain(version = "1.36.5")
Checksums for versions the module does not know about are looked up from the
upstream .sha256 files while the extension is evaluated. On Bazel 8.5 and
newer the digests are then stored as facts in MODULE.bazel.lock, so the
lookup happens once per version and airgapped builds keep working from the
lockfile. Older Bazel versions repeat the lookup whenever the extension is
re-evaluated.
Several versions can live side by side by giving each tag its own repository
name. Only the first toolchain tag in the root module decides which version
the registered toolchains serve:
kubectl.toolchain()
kubectl.toolchain(name = "kubectl_legacy", version = "1.35.6")
use_repo(kubectl, "kubectl", "kubectl_legacy")
Use in a genrule
The resolved toolchain exposes the binary through two make variables:
KUBECTL is its execpath, for use in actions, and KUBECTL_RLOCATIONPATH is
its rlocationpath, for looking it up in runfiles.
genrule(
name = "kubectl_version",
outs = ["kubectl_version.json"],
cmd = "$(KUBECTL) version --client --output json > $@",
toolchains = ["@rules_kubectl//kubectl:current_toolchain"],
)
sh_test(
name = "kubectl_version_test",
srcs = ["kubectl_version_test.sh"],
data = ["@rules_kubectl//kubectl:current_toolchain"],
env = {"KUBECTL": "$(KUBECTL_RLOCATIONPATH)"},
toolchains = ["@rules_kubectl//kubectl:current_toolchain"],
)
For rule authors
Depend on the toolchain type and read the binary from the resolved
ToolchainInfo:
def _impl(ctx):
toolchain = ctx.toolchains["@rules_kubectl//kubectl:toolchain_type"]
ctx.actions.run(
executable = toolchain.kubectl,
...
)
my_rule = rule(
implementation = _impl,
toolchains = ["@rules_kubectl//kubectl:toolchain_type"],
)
ToolchainInfo carries kubectl (the binary File) and version (the
version string, without a leading v).
Updating the default version
bazel run //tools:fetch_checksums -- 1.38.0
prints the table entry for kubectl/private/versions.bzl. Add it, update
DEFAULT_VERSION, and remove the previous entry.
rules_kubectl
Bazel rules for a hermetic kubectl.
@kubectl//:kubectlruns the host platform's binary from the directorybazel runwas invoked in.@rules_kubectl//kubectl:toolchain_typeis the toolchain type rules consume to runkubectlin an action.@rules_kubectl//kubectl:current_toolchainexposes the resolved binary togenruleand friends as theKUBECTL(execpath) andKUBECTL_RLOCATIONPATH(rlocationpath) make variables.
Rules
kubectl_toolchain
load("@rules_kubectl//kubectl:defs.bzl", "kubectl_toolchain")
kubectl_toolchain(name, kubectl, version)
A kubectl toolchain.
Rules consume it through @rules_kubectl//kubectl:toolchain_type:
def _impl(ctx):
toolchain = ctx.toolchains["@rules_kubectl//kubectl:toolchain_type"]
ctx.actions.run(
executable = toolchain.kubectl,
...
)
The ToolchainInfo carries kubectl (the binary File) and version (a string, possibly empty).
@rules_kubectl//kubectl:current_toolchain exposes the binary through two make variables: KUBECTL is its execpath, for use in actions, and KUBECTL_RLOCATIONPATH is its rlocationpath, for looking it up in runfiles.
genrule(
name = "pods",
outs = ["pods.txt"],
cmd = "$(KUBECTL) version --client > $@",
toolchains = ["@rules_kubectl//kubectl:current_toolchain"],
)
sh_test(
name = "pods_test",
srcs = ["pods_test.sh"],
data = ["@rules_kubectl//kubectl:current_toolchain"],
env = {"KUBECTL": "$(KUBECTL_RLOCATIONPATH)"},
toolchains = ["@rules_kubectl//kubectl:current_toolchain"],
)
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| kubectl | A kubectl binary. | Label | required | |
| version | The version of kubectl, without a leading v. Informational. | String | optional | "" |
Bzlmod extension for kubectl
Module Extensions
kubectl
kubectl = use_extension("@rules_kubectl//kubectl:extensions.bzl", "kubectl")
kubectl.toolchain(name, version)
Module extension for fetching kubectl.
TAG CLASSES
toolchain
Fetch kubectl and register toolchains for it.
Each tag creates a host repository (default @kubectl) exposing the host platform's binary as @{name}//:kubectl. That target runs kubectl from the directory bazel run was invoked in, so relative paths such as -f ./deploy.yaml resolve as they would for a locally installed kubectl.
kubectl = use_extension("@rules_kubectl//kubectl:extensions.bzl", "kubectl")
kubectl.toolchain()
use_repo(kubectl, "kubectl")
bazel run @kubectl -- get pods --namespace my-namespace
bazel run @kubectl -- apply -f ./deploy/configmap.yaml
rules_kubectl registers the toolchains itself; the root module does not need a register_toolchains call. The registered toolchains serve the version requested by the root module's first toolchain tag (or the default when the root module has none).
Versions
rules_kubectl ships checksums for a single default version. Requesting any other version makes the extension download the upstream .sha256 files for that release while it is evaluated. On Bazel 8.5 and newer the digests are then stored as facts in MODULE.bazel.lock, so the lookup only happens once per version and airgapped builds work from the lockfile. Older Bazel versions repeat the lookup whenever the extension is re-evaluated.
kubectl.toolchain(version = "1.36.5")
Several versions can live side by side by giving each tag its own repository name. Only the first tag in the root module decides which version the registered toolchains serve:
kubectl.toolchain()
kubectl.toolchain(name = "kubectl_legacy", version = "1.35.6")
use_repo(kubectl, "kubectl", "kubectl_legacy")
Attributes
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | The name of the host repository. | Name | optional | "kubectl" |
| version | The version of kubectl to fetch, without a leading v. | String | optional | "1.37.1" |