Files
velero/Tiltfile
2026-11-06 13:06:01 +08:00

314 lines
12 KiB
Python

# -*- mode: Python -*-
# The CRDs are globbed rather than listed, so that a dev branch adding one is
# picked up without editing this file. Globbing needs a local() call, which Tilt
# refuses while the current kube context might be a production one, so the
# k8s_yaml() for it lives below allow_k8s_contexts().
# default values
settings = {
"default_registry": "docker.io/velero",
"use_node_agent": False,
"enable_debug": False,
"debug_continue_on_start": True, # Continue the velero process by default when in debug mode
"create_backup_locations": False,
"setup-minio": False,
# Both are detected when left unset; set them only to override:
# cluster_goarch: "amd64" or "arm64" -- the target cluster's node arch
}
# global settings
settings.update(read_json(
"tilt-resources/tilt-settings.json",
default = {},
))
k8s_yaml(kustomize('tilt-resources'))
k8s_yaml('tilt-resources/deployment.yaml')
if settings.get("enable_debug"):
k8s_resource('velero', port_forwards = '2345')
# TODO: Need to figure out how to apply port forwards for all node-agent pods
if settings.get("use_node_agent"):
k8s_yaml('tilt-resources/node-agent.yaml')
if settings.get("create_backup_locations"):
k8s_yaml('tilt-resources/velero_v1_backupstoragelocation.yaml')
if settings.get("setup-minio"):
k8s_yaml('examples/minio/00-minio-deployment.yaml', allow_duplicates = True)
# By default, Tilt automatically allows Minikube, Docker for Desktop, Microk8s, Red Hat CodeReady Containers, Kind, K3D, and Krucible.
allow_k8s_contexts(settings.get("allowed_contexts"))
# Every CRD under config/crd, so a branch that adds one needs no edit here.
# Deliberately after allow_k8s_contexts(): see the note at the top of the file.
k8s_yaml(str(local('ls config/crd/*/bases/*.yaml', quiet = True, echo_off = True)).splitlines())
# An empty registry means "local cluster, don't push" (kind loads the image
# directly), and Tilt rejects default_registry("") as a missing host.
if settings.get("default_registry"):
default_registry(settings.get("default_registry"))
local_goos = str(local("go env GOOS", quiet = True, echo_off = True)).strip()
local_goarch = str(local("go env GOARCH", quiet = True, echo_off = True)).strip()
git_sha = str(local("git rev-parse HEAD", quiet = True, echo_off = True)).strip()
def detect_cluster_goarch():
"""
Best-effort node architecture of the target cluster.
Every binary in the image and the base image must match the node arch, and
getting it wrong shows up only later as an "exec format error" in the pod.
The cluster usually knows the answer, so ask it. Any failure (no cluster
yet, no nodes, kubectl missing) yields "" and the caller falls back to the
local architecture, which is correct when the cluster shares the machine
(kind, minikube, Docker Desktop).
"""
probe = "kubectl get nodes -o jsonpath='{.items[0].status.nodeInfo.architecture}' 2>/dev/null || true"
return str(local(probe, quiet = True, echo_off = True)).strip()
cluster_goarch = settings.get("cluster_goarch") or detect_cluster_goarch() or local_goarch
def get_build_registry():
"""
Value for hack/build.sh's REGISTRY, which refuses an empty one. It is only
baked into the binary as the default image prefix; it is not where Tilt
pushes. An empty default_registry means "local cluster, don't push", so
fall back to the stock prefix for the build.
"""
return settings.get("default_registry") or "docker.io/velero"
# Docker's static CLI download archives use their own arch names.
docker_cli_arch = {"amd64": "x86_64", "arm64": "aarch64"}.get(cluster_goarch, cluster_goarch)
print("Tilt: cluster_goarch={} (local {})".format(cluster_goarch, local_goarch))
tilt_helper_dockerfile_header = """
# Tilt image
FROM golang:1.27.3-trixie as tilt-helper
# Support live reloading with Tilt
RUN wget --output-document /restart.sh --quiet https://raw.githubusercontent.com/windmilleng/rerun-process-wrapper/master/restart.sh && \
wget --output-document /start.sh --quiet https://raw.githubusercontent.com/windmilleng/rerun-process-wrapper/master/start.sh && \
chmod +x /start.sh && chmod +x /restart.sh
"""
# Pinned so the debugger in the image does not change under you between
# builds; @latest made every rebuild a different dlv.
#
# Delve refuses a binary built by a newer Go than it understands ("Version of
# Delve is too old for Go version go1.x.y"), so this pin has to move together
# with the golang image below.
DLV_VERSION = "v1.27.2"
additional_docker_helper_commands = """
# Install delve to allow debugging. A cross-compiled `go install` places the
# binary in $GOPATH/bin/linux_<arch>/ rather than $GOPATH/bin/.
RUN CGO_ENABLED=0 GOOS=linux GOARCH=%s go install github.com/go-delve/delve/cmd/dlv@%s && \\
if [ -x "$(go env GOPATH)/bin/dlv" ]; then cp "$(go env GOPATH)/bin/dlv" /usr/local/bin/dlv; \\
else cp "$(go env GOPATH)/bin/linux_%s/dlv" /usr/local/bin/dlv; fi
RUN wget -qO- https://dl.k8s.io/v1.25.2/kubernetes-client-linux-%s.tar.gz | tar xvz -C /tmp
RUN wget -qO- https://download.docker.com/linux/static/stable/%s/docker-27.5.1.tgz | tar xvz -C /tmp && \\
install -D /tmp/docker/docker /usr/local/bin/docker
""" % (cluster_goarch, DLV_VERSION, cluster_goarch, cluster_goarch, docker_cli_arch)
additional_docker_build_commands = """
COPY --from=tilt-helper /usr/local/bin/dlv /usr/bin/dlv
COPY --from=tilt-helper /usr/local/bin/docker /usr/bin/docker
COPY --from=tilt-helper /tmp/kubernetes/client/bin/kubectl /usr/bin/kubectl
"""
##############################
# Setup Velero
##############################
def get_debug_flag():
"""
Returns the flag to enable debug building of Velero if debug
mode is enabled.
"""
if settings.get('enable_debug'):
return "DEBUG=1"
return ""
# Set up a local_resource build of the Velero binary. The binary is written to _tiltbuild/velero.
# GOBIN is required by hack/build.sh and is not otherwise set in a fresh shell.
local_resource(
"velero_server_binary",
cmd = 'cd ' + '.' + ';mkdir -p _tiltbuild;PKG=. BIN=velero GOBIN=$(pwd)/_tiltbuild GOOS=linux GOARCH=' + cluster_goarch + ' GIT_SHA=' + git_sha + ' VERSION=main GIT_TREE_STATE=dirty OUTPUT_DIR=_tiltbuild ' + get_debug_flag() + ' REGISTRY=' + get_build_registry() + ' ./hack/build.sh',
deps = ["cmd", "internal", "pkg"],
ignore = ["pkg/cmd"],
)
local_resource(
"velero_local_binary",
cmd = 'cd ' + '.' + ';mkdir -p _tiltbuild/local;PKG=. BIN=velero GOBIN=$(pwd)/_tiltbuild/local GOOS=' + local_goos + ' GOARCH=' + local_goarch + ' GIT_SHA=' + git_sha + ' VERSION=main GIT_TREE_STATE=dirty OUTPUT_DIR=_tiltbuild/local ' + get_debug_flag() + ' REGISTRY=' + get_build_registry() + ' ./hack/build.sh',
deps = ["internal", "pkg/cmd"],
)
# Note: we need a distro with a bash shell to exec into the Velero container.
# The runtime stage is built for the cluster's architecture; its apt-get RUN
# executes under emulation (Rosetta/qemu) when that differs from the build
# machine's.
tilt_dockerfile_header = """
FROM --platform=linux/%s ubuntu:22.04 as tilt
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -qq -y ca-certificates tzdata && rm -rf /var/lib/apt/lists/*
WORKDIR /
COPY --from=tilt-helper /start.sh .
COPY --from=tilt-helper /restart.sh .
COPY velero .
""" % cluster_goarch
dockerfile_contents = "\n".join([
tilt_helper_dockerfile_header,
additional_docker_helper_commands,
tilt_dockerfile_header,
additional_docker_build_commands,
])
def get_velero_entrypoint():
"""
Returns the entrypoint for the Velero container image.
"""
entrypoint = ["sh", "/start.sh"]
if settings.get("enable_debug"):
# If debug mode is enabled, start the velero process using Delve
entrypoint.extend(
["dlv", "--listen=:2345", "--headless=true", "--api-version=2", "--accept-multiclient", "exec"])
# Set whether or not to continue the debugged process on start
# See https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_exec.md
if settings.get("debug_continue_on_start"):
entrypoint.append("--continue")
entrypoint.append("--")
entrypoint.append("/velero")
return entrypoint
# Set up an image build for Velero. The live update configuration syncs the output from the local_resource
# build into the container.
docker_build(
ref = "velero/velero",
context = "_tiltbuild",
dockerfile_contents = dockerfile_contents,
target = "tilt",
platform = "linux/" + cluster_goarch,
entrypoint = get_velero_entrypoint(),
live_update = [
sync("./_tiltbuild/velero", "/velero"),
run("sh /restart.sh"),
])
##############################
# Setup plugins
##############################
def load_provider_tiltfiles():
all_providers = settings.get("providers", {})
enable_providers = settings.get("enable_providers", [])
providers = []
## Load settings only for providers to enable
for name in enable_providers:
repo = all_providers.get(name)
if not repo:
print("Enabled provider '{}' does not exist in list of supported providers".format(name))
continue
file = repo + "/tilt-provider.json"
if not os.path.exists(file):
print("Provider settings not found for \"{}\". Please ensure this plugin repository has a tilt-provider.json file included.".format(name))
continue
provider_details = read_json(file, default = {})
if type(provider_details) == "dict":
provider_details["name"] = name
if "context" in provider_details:
provider_details["context"] = os.path.join(repo, "/", provider_details["context"])
else:
provider_details["context"] = repo
if "go_main" not in provider_details:
provider_details["go_main"] = "main.go"
providers.append(provider_details)
return providers
# Enable each provider
def enable_providers(providers):
if not providers:
print("No providers to enable.")
return
for p in providers:
enable_provider(p)
# Configures a provider by doing the following:
#
# 1. Enables a local_resource go build of the provider's local binary
# 2. Configures a docker build for the provider, with live updating of the local binary
def enable_provider(provider):
name = provider.get("name")
plugin_name = provider.get("plugin_name")
# Note: we need a distro with a shell to do a copy of the plugin binary
tilt_dockerfile_header = """
FROM --platform=linux/""" + cluster_goarch + """ ubuntu:22.04 as tilt
WORKDIR /
COPY --from=tilt-helper /start.sh .
COPY --from=tilt-helper /restart.sh .
COPY """ + plugin_name + """ .
"""
dockerfile_contents = "\n".join([
tilt_helper_dockerfile_header,
additional_docker_helper_commands,
tilt_dockerfile_header,
additional_docker_build_commands,
])
context = provider.get("context")
go_main = provider.get("go_main", "main.go")
live_reload_deps = []
for d in provider.get("live_reload_deps", []):
live_reload_deps.append(os.path.join(context, "/", d))
# Set up a local_resource build of the plugin binary. The main.go path must be provided via go_main option. The binary is written to _tiltbuild/<NAME>.
local_resource(
name + "_plugin",
cmd = 'cd ' + context + ';mkdir -p _tiltbuild;PKG=' + context + ' BIN=' + go_main + ' GOOS=linux GOARCH=' + cluster_goarch + ' OUTPUT_DIR=_tiltbuild ./hack/build.sh',
deps = live_reload_deps,
)
# Set up an image build for the plugin. The live update configuration syncs the output from the local_resource
# build into the init container, and that restarts the Velero container.
docker_build(
ref = provider.get("image"),
context = os.path.join(context, "/_tiltbuild/"),
dockerfile_contents = dockerfile_contents,
target = "tilt",
entrypoint = ["/bin/bash", "-c", "cp /" + plugin_name + " /target/."],
live_update = [
sync(os.path.join(context, "/_tiltbuild/", plugin_name), os.path.join("/", plugin_name))
]
)
##############################
# Start
#############################
enable_providers(load_provider_tiltfiles())