#!/usr/bin/env bash

# Copyright 2026 PingCAP, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -euo pipefail

KIND="${KIND:-bin/kind}"
KUBECTL="${KUBECTL:-kubectl}"
CONTAINER_TOOL="${CONTAINER_TOOL:-docker}"
OPENSSL="${OPENSSL:-openssl}"
KIND_NODE_IMAGE="${KIND_NODE_IMAGE:-kindest/node:v1.36.1}"
KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:-capacity-planner}"
KIND_API_SERVER_ADDRESS="${KIND_API_SERVER_ADDRESS:-172.17.0.1}"
KIND_API_SERVER_PORT="${KIND_API_SERVER_PORT:-36443}"
KIND_REGISTRY_IMAGE="${KIND_REGISTRY_IMAGE:-registry:2}"
KIND_REGISTRY_LOAD_BALANCER_IP="${KIND_REGISTRY_LOAD_BALANCER_IP:-}"
KIND_GRAFANA_LOAD_BALANCER_IP="${KIND_GRAFANA_LOAD_BALANCER_IP:-}"
KIND_REGISTRY_CERT_DIR="${KIND_REGISTRY_CERT_DIR:-$(pwd)/build/bin/kube/registry}"
KIND_NETWORK="${KIND_NETWORK:-kind}"
BUILDKIT_IMAGE="${BUILDKIT_IMAGE:-moby/buildkit:v0.31.2-rootless}"
BUILDKIT_NAMESPACE="${BUILDKIT_NAMESPACE:-buildkit}"
BUILDKIT_NAME="${BUILDKIT_NAME:-buildkitd}"
METALLB_MANIFEST="${METALLB_MANIFEST:-https://raw.githubusercontent.com/metallb/metallb/v0.16.1/config/manifests/metallb-native.yaml}"
KUBECONFIG="${KUBECONFIG:-$(pwd)/kubeconfig}"

REGISTRY_NAMESPACE="local-registry"
REGISTRY_NAME="local-registry"
REGISTRY_PORT=5000
CONTEXT="kind-${KIND_CLUSTER_NAME}"
CONTROL_PLANE="${KIND_CLUSTER_NAME}-control-plane"

kind_config=""
cert_temp_dir=""
cluster_created=0
kubeconfig_created=0

fail() {
	echo "deploy/kube: $*" >&2
	exit 1
}

require_command() {
	local command_name="$1"
	if [[ "$command_name" == */* ]]; then
		[[ -x "$command_name" ]] || fail "command is not executable: ${command_name}"
	else
		command -v "$command_name" >/dev/null 2>&1 || fail "command is not executable: ${command_name}"
	fi
}

ipv4_to_integer() {
	local address="$1"
	local first second third fourth extra
	IFS=. read -r first second third fourth extra <<<"$address"
	[[ -z "${extra:-}" && -n "${fourth:-}" ]] || return 1
	local octet
	for octet in "$first" "$second" "$third" "$fourth"; do
		[[ "$octet" =~ ^(0|[1-9][0-9]*)$ ]] || return 1
		((octet >= 0 && octet <= 255)) || return 1
	done
	printf '%u' "$(((first << 24) | (second << 16) | (third << 8) | fourth))"
}

integer_to_ipv4() {
	local value="$1"
	printf '%d.%d.%d.%d' \
		"$(((value >> 24) & 255))" \
		"$(((value >> 16) & 255))" \
		"$(((value >> 8) & 255))" \
		"$((value & 255))"
}

default_load_balancer_ip() {
	local subnets subnet candidate network_address prefix network_integer host_count broadcast
	subnets="$(
		"$CONTAINER_TOOL" network inspect --format \
			'{{range .IPAM.Config}}{{println .Subnet}}{{end}}' "$KIND_NETWORK"
	)"
	subnet=""
	while IFS= read -r candidate; do
		if [[ "$candidate" == *.*/* ]]; then
			subnet="$candidate"
			break
		fi
	done <<<"$subnets"
	[[ -n "$subnet" ]] || fail "could not find an IPv4 subnet for container network ${KIND_NETWORK}"
	network_address="${subnet%/*}"
	prefix="${subnet#*/}"
	[[ "$prefix" =~ ^[1-9][0-9]*$ ]] || fail "invalid container network CIDR: ${subnet}"
	((prefix >= 8 && prefix <= 27)) ||
		fail "container network must have an IPv4 /8 through /27 subnet: ${subnet}"
	network_integer="$(ipv4_to_integer "$network_address")" ||
		fail "invalid container network CIDR: ${subnet}"
	host_count=$((1 << (32 - prefix)))
	broadcast=$(((network_integer & (0xFFFFFFFF << (32 - prefix))) + host_count - 1))
	# Docker/Podman allocate Kind node addresses from the bottom of the subnet.
	# Reserve one address near the top, following the conventional Kind+MetalLB
	# layout while leaving room for the broadcast address.
	integer_to_ipv4 "$((broadcast - 10))"
}

ensure_registry_certificates() {
	local address="$1"
	local cert_modulus key_modulus
	local reusable=0
	registry_ca_key="${KIND_REGISTRY_CERT_DIR}/ca.key"
	registry_ca_cert="${KIND_REGISTRY_CERT_DIR}/ca.crt"
	registry_tls_key="${KIND_REGISTRY_CERT_DIR}/tls.key"
	registry_tls_cert="${KIND_REGISTRY_CERT_DIR}/tls.crt"

	mkdir -p "$KIND_REGISTRY_CERT_DIR"
	chmod 700 "$KIND_REGISTRY_CERT_DIR"
	if [[ -r "$registry_ca_key" && -r "$registry_ca_cert" && -r "$registry_tls_key" && -r "$registry_tls_cert" ]] &&
		"$OPENSSL" verify -CAfile "$registry_ca_cert" -verify_ip "$address" "$registry_tls_cert" >/dev/null 2>&1 &&
		"$OPENSSL" x509 -checkend 86400 -noout -in "$registry_ca_cert" >/dev/null 2>&1 &&
		"$OPENSSL" x509 -checkend 86400 -noout -in "$registry_tls_cert" >/dev/null 2>&1; then
		cert_modulus="$("$OPENSSL" x509 -noout -modulus -in "$registry_tls_cert" 2>/dev/null)"
		key_modulus="$("$OPENSSL" rsa -noout -modulus -in "$registry_tls_key" 2>/dev/null)"
		[[ "$cert_modulus" == "$key_modulus" ]] && reusable=1
	fi
	if ((reusable)); then
		echo "deploy/kube: reusing registry TLS certificate for ${address}"
		return
	fi

	echo "deploy/kube: generating registry TLS certificate for ${address}"
	cert_temp_dir="$(mktemp -d "${KIND_REGISTRY_CERT_DIR}/.generate.XXXXXX")"
	(
		umask 077
		"$OPENSSL" req -x509 -newkey rsa:4096 -nodes -sha256 -days 3650 \
			-keyout "${cert_temp_dir}/ca.key" \
			-out "${cert_temp_dir}/ca.crt" \
			-subj "/CN=${KIND_CLUSTER_NAME} local registry CA" \
			-addext 'basicConstraints=critical,CA:TRUE' \
			-addext 'keyUsage=critical,keyCertSign,cRLSign' >/dev/null 2>&1
		"$OPENSSL" req -newkey rsa:2048 -nodes -sha256 \
			-keyout "${cert_temp_dir}/tls.key" \
			-out "${cert_temp_dir}/tls.csr" \
			-subj "/CN=${address}" >/dev/null 2>&1
		cat >"${cert_temp_dir}/tls.ext" <<EOF
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=IP:${address}
EOF
		"$OPENSSL" x509 -req -sha256 -days 825 \
			-in "${cert_temp_dir}/tls.csr" \
			-CA "${cert_temp_dir}/ca.crt" \
			-CAkey "${cert_temp_dir}/ca.key" \
			-set_serial 1 \
			-extfile "${cert_temp_dir}/tls.ext" \
			-out "${cert_temp_dir}/tls.crt" >/dev/null 2>&1
	)
	mv "${cert_temp_dir}/ca.key" "$registry_ca_key"
	mv "${cert_temp_dir}/ca.crt" "$registry_ca_cert"
	mv "${cert_temp_dir}/tls.key" "$registry_tls_key"
	mv "${cert_temp_dir}/tls.crt" "$registry_tls_cert"
	chmod 600 "$registry_ca_key" "$registry_tls_key"
	chmod 644 "$registry_ca_cert" "$registry_tls_cert"
	rm -rf "$cert_temp_dir"
	cert_temp_dir=""
}

on_exit() {
	local status=$?
	if [[ -n "$kind_config" ]]; then
		rm -f "$kind_config"
	fi
	if [[ -n "$cert_temp_dir" ]]; then
		rm -rf "$cert_temp_dir"
	fi
	if ((status != 0)); then
		if ((cluster_created)); then
			echo "deploy/kube: creation failed; deleting cluster ${KIND_CLUSTER_NAME}" >&2
			"$KIND" delete cluster --name "$KIND_CLUSTER_NAME" >/dev/null 2>&1 || true
		fi
		if ((kubeconfig_created)); then
			rm -f "$KUBECONFIG"
		fi
	fi
	exit "$status"
}

trap on_exit EXIT
trap 'exit 130' INT TERM

require_command "$KIND"
require_command "$KUBECTL"
require_command "$CONTAINER_TOOL"
require_command "$OPENSSL"
[[ "$KIND_CLUSTER_NAME" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]] ||
	fail "KIND_CLUSTER_NAME must be a DNS label: ${KIND_CLUSTER_NAME}"
ipv4_to_integer "$KIND_API_SERVER_ADDRESS" >/dev/null ||
	fail "KIND_API_SERVER_ADDRESS must be an IPv4 address: ${KIND_API_SERVER_ADDRESS}"
[[ "$KIND_API_SERVER_PORT" =~ ^[1-9][0-9]*$ ]] &&
	((KIND_API_SERVER_PORT <= 65535)) ||
	fail "KIND_API_SERVER_PORT must be between 1 and 65535: ${KIND_API_SERVER_PORT}"
[[ "$KIND_REGISTRY_IMAGE" != *[[:space:]]* ]] ||
	fail "KIND_REGISTRY_IMAGE must not contain whitespace"
[[ "$BUILDKIT_IMAGE" != *[[:space:]]* ]] ||
	fail "BUILDKIT_IMAGE must not contain whitespace"
[[ "$BUILDKIT_NAMESPACE" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]] ||
	fail "BUILDKIT_NAMESPACE must be a DNS label: ${BUILDKIT_NAMESPACE}"
[[ "$BUILDKIT_NAME" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]] ||
	fail "BUILDKIT_NAME must be a DNS label: ${BUILDKIT_NAME}"
[[ -n "$METALLB_MANIFEST" && "$METALLB_MANIFEST" != *[[:space:]]* ]] ||
	fail "METALLB_MANIFEST must be a non-empty URL or file path without whitespace"

cluster_list="$("$KIND" get clusters)"
cluster_exists=0
while IFS= read -r cluster; do
	if [[ "$cluster" == "$KIND_CLUSTER_NAME" ]]; then
		cluster_exists=1
		break
	fi
done <<<"$cluster_list"

if ((!cluster_exists)); then
	[[ ! -e "$KUBECONFIG" ]] ||
		fail "refusing to overwrite kubeconfig for a new cluster: ${KUBECONFIG}"
	mkdir -p "$(dirname "$KUBECONFIG")"
	kind_config="$(mktemp "${TMPDIR:-/tmp}/capacity-planner-kind.XXXXXX")"
	cat >"$kind_config" <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
  [plugins."io.containerd.grpc.v1.cri".registry]
    config_path = "/etc/containerd/certs.d"
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: ClusterConfiguration
    apiServer:
      certSANs:
      - "${KIND_API_SERVER_ADDRESS}"
  extraPortMappings:
  - containerPort: 6443
    hostPort: ${KIND_API_SERVER_PORT}
    listenAddress: "${KIND_API_SERVER_ADDRESS}"
    protocol: TCP
- role: worker
- role: worker
- role: worker
EOF

	cluster_created=1
	kubeconfig_created=1
	echo "deploy/kube: creating four-node Kind cluster ${KIND_CLUSTER_NAME}"
	"$KIND" create cluster \
		--name "$KIND_CLUSTER_NAME" \
		--image "$KIND_NODE_IMAGE" \
		--config "$kind_config" \
		--kubeconfig "$KUBECONFIG"
else
	echo "deploy/kube: reusing Kind cluster ${KIND_CLUSTER_NAME}"
	if [[ ! -e "$KUBECONFIG" ]]; then
		kubeconfig_created=1
	fi
	mkdir -p "$(dirname "$KUBECONFIG")"
	# Refresh CA and client credentials as well as the endpoint. A kubeconfig
	# left by an older cluster can use the same context name but a different CA.
	"$KIND" export kubeconfig --name "$KIND_CLUSTER_NAME" --kubeconfig "$KUBECONFIG"
	configured_context="$(
		"$KUBECTL" --kubeconfig "$KUBECONFIG" config get-contexts "$CONTEXT" -o name
	)"
	[[ "$configured_context" == "$CONTEXT" ]] ||
		fail "kubeconfig does not contain context ${CONTEXT}: ${KUBECONFIG}"
fi

nodes_output="$("$KIND" get nodes --name "$KIND_CLUSTER_NAME")"
nodes=()
while IFS= read -r node; do
	nodes+=("$node")
done <<<"$nodes_output"
expected_nodes=(
	"${KIND_CLUSTER_NAME}-control-plane"
	"${KIND_CLUSTER_NAME}-worker"
	"${KIND_CLUSTER_NAME}-worker2"
	"${KIND_CLUSTER_NAME}-worker3"
)
[[ "${#nodes[@]}" -eq "${#expected_nodes[@]}" ]] ||
	fail "cluster must contain exactly four nodes; found ${#nodes[@]}"
for expected_node in "${expected_nodes[@]}"; do
	found=0
	for node in "${nodes[@]}"; do
		if [[ "$node" == "$expected_node" ]]; then
			found=1
			break
		fi
	done
	((found)) || fail "cluster is missing expected node ${expected_node}"
done

published_ports="$(
	"$CONTAINER_TOOL" inspect --format \
		'{{range $port, $_ := .HostConfig.PortBindings}}{{println $port}}{{end}}' \
		"$CONTROL_PLANE"
)"
while IFS= read -r published_port; do
	[[ -z "$published_port" || "$published_port" == "6443/tcp" ]] && continue
	fail "cluster has unsupported host port mapping ${published_port}; recreate ${KIND_CLUSTER_NAME} with make deploy/kube"
done <<<"$published_ports"

api_server_mapping="$("$CONTAINER_TOOL" inspect --format \
	'{{range (index .HostConfig.PortBindings "6443/tcp")}}{{println .HostIp .HostPort}}{{end}}' \
	"$CONTROL_PLANE")"
expected_api_server_mapping="${KIND_API_SERVER_ADDRESS} ${KIND_API_SERVER_PORT}"
mapping_found=0
while IFS= read -r mapping; do
	if [[ "$mapping" == "$expected_api_server_mapping" ]]; then
		mapping_found=1
		break
	fi
done <<<"$api_server_mapping"
((mapping_found)) ||
	fail "cluster API server is not published at ${KIND_API_SERVER_ADDRESS}:${KIND_API_SERVER_PORT}; recreate ${KIND_CLUSTER_NAME} with make deploy/kube"

"$KUBECTL" --kubeconfig "$KUBECONFIG" config set-cluster "$CONTEXT" \
	--server="https://${KIND_API_SERVER_ADDRESS}:${KIND_API_SERVER_PORT}" >/dev/null

echo "deploy/kube: waiting for all four Kubernetes nodes"
"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" wait \
	--for=condition=Ready --timeout=5m node --all

echo "deploy/kube: installing MetalLB inside the cluster"
"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" apply \
	-f "$METALLB_MANIFEST"
"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" wait \
	--for=condition=Established --timeout=5m \
	crd/ipaddresspools.metallb.io \
	crd/l2advertisements.metallb.io
"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" \
	-n metallb-system rollout status --timeout=5m deployment/controller
"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" \
	-n metallb-system rollout status --timeout=5m daemonset/speaker

registry_load_balancer_ip="$KIND_REGISTRY_LOAD_BALANCER_IP"
if [[ -z "$registry_load_balancer_ip" ]]; then
	registry_load_balancer_ip="$(default_load_balancer_ip)"
else
	ipv4_to_integer "$registry_load_balancer_ip" >/dev/null ||
		fail "KIND_REGISTRY_LOAD_BALANCER_IP must be an IPv4 address: ${registry_load_balancer_ip}"
fi
grafana_load_balancer_ip="$KIND_GRAFANA_LOAD_BALANCER_IP"
if [[ -z "$grafana_load_balancer_ip" ]]; then
	registry_load_balancer_integer="$(ipv4_to_integer "$registry_load_balancer_ip")"
	((registry_load_balancer_integer > 0)) ||
		fail "cannot derive Grafana LoadBalancer address from ${registry_load_balancer_ip}"
	grafana_load_balancer_ip="$(integer_to_ipv4 "$((registry_load_balancer_integer - 1))")"
else
	ipv4_to_integer "$grafana_load_balancer_ip" >/dev/null ||
		fail "KIND_GRAFANA_LOAD_BALANCER_IP must be an IPv4 address: ${grafana_load_balancer_ip}"
fi
[[ "$grafana_load_balancer_ip" != "$registry_load_balancer_ip" ]] ||
	fail "Grafana and registry LoadBalancer addresses must be different: ${grafana_load_balancer_ip}"
ensure_registry_certificates "$registry_load_balancer_ip"
registry_tls_fingerprint="$(
	"$OPENSSL" x509 -noout -fingerprint -sha256 -in "$registry_tls_cert"
)"

echo "deploy/kube: configuring MetalLB addresses ${registry_load_balancer_ip} and ${grafana_load_balancer_ip}"
"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" apply -f - <<EOF
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: kind-registry
  namespace: metallb-system
spec:
  autoAssign: false
  addresses:
  - ${registry_load_balancer_ip}/32
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: kind-registry
  namespace: metallb-system
spec:
  ipAddressPools:
  - kind-registry
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: kind-grafana
  namespace: metallb-system
spec:
  autoAssign: false
  addresses:
  - ${grafana_load_balancer_ip}/32
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: kind-grafana
  namespace: metallb-system
spec:
  ipAddressPools:
  - kind-grafana
EOF

"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: ${REGISTRY_NAMESPACE}
EOF
"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" \
	-n "$REGISTRY_NAMESPACE" create secret tls local-registry-tls \
	--cert="$registry_tls_cert" --key="$registry_tls_key" \
	--dry-run=client -o yaml |
	"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" \
		-n "$REGISTRY_NAMESPACE" apply -f -

existing_registry_node_port="$(
	"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" \
		-n "$REGISTRY_NAMESPACE" get service "$REGISTRY_NAME" \
		-o jsonpath='{.spec.ports[0].nodePort}' 2>/dev/null || true
)"
if [[ -n "$existing_registry_node_port" && "$existing_registry_node_port" != "0" ]]; then
	echo "deploy/kube: replacing legacy registry Service that allocated NodePort ${existing_registry_node_port}"
	"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" \
		-n "$REGISTRY_NAMESPACE" delete service "$REGISTRY_NAME" --wait=true
fi

echo "deploy/kube: installing registry Deployment and LoadBalancer Service inside the cluster"
"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ${REGISTRY_NAME}
  namespace: ${REGISTRY_NAMESPACE}
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app.kubernetes.io/name: ${REGISTRY_NAME}
  template:
    metadata:
      annotations:
        checksum/registry-tls: "${registry_tls_fingerprint}"
      labels:
        app.kubernetes.io/name: ${REGISTRY_NAME}
    spec:
      nodeSelector:
        kubernetes.io/hostname: ${CONTROL_PLANE}
      tolerations:
      - key: node-role.kubernetes.io/control-plane
        operator: Exists
        effect: NoSchedule
      containers:
      - name: registry
        image: ${KIND_REGISTRY_IMAGE}
        imagePullPolicy: IfNotPresent
        env:
        - name: REGISTRY_HTTP_TLS_CERTIFICATE
          value: /certs/tls.crt
        - name: REGISTRY_HTTP_TLS_KEY
          value: /certs/tls.key
        ports:
        - name: registry
          containerPort: 5000
        readinessProbe:
          httpGet:
            path: /v2/
            port: registry
            scheme: HTTPS
        livenessProbe:
          httpGet:
            path: /v2/
            port: registry
            scheme: HTTPS
        volumeMounts:
        - name: registry-data
          mountPath: /var/lib/registry
        - name: registry-tls
          mountPath: /certs
          readOnly: true
      volumes:
      - name: registry-data
        hostPath:
          path: /var/local-registry
          type: DirectoryOrCreate
      - name: registry-tls
        secret:
          secretName: local-registry-tls
---
apiVersion: v1
kind: Service
metadata:
  name: ${REGISTRY_NAME}
  namespace: ${REGISTRY_NAMESPACE}
  annotations:
    metallb.io/address-pool: kind-registry
    metallb.io/loadBalancerIPs: "${registry_load_balancer_ip}"
spec:
  type: LoadBalancer
  allocateLoadBalancerNodePorts: false
  selector:
    app.kubernetes.io/name: ${REGISTRY_NAME}
  ports:
  - name: registry
    port: 5000
    targetPort: registry
EOF

"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" \
	-n "$REGISTRY_NAMESPACE" rollout status \
	--timeout=5m "deployment/${REGISTRY_NAME}"

"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" \
	-n "$REGISTRY_NAMESPACE" wait --timeout=5m \
	--for="jsonpath={.status.loadBalancer.ingress[0].ip}=${registry_load_balancer_ip}" \
	"service/${REGISTRY_NAME}"
assigned_load_balancer_ip="$(
	"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" \
		-n "$REGISTRY_NAMESPACE" get service "$REGISTRY_NAME" \
		-o jsonpath='{.status.loadBalancer.ingress[0].ip}'
)"
[[ "$assigned_load_balancer_ip" == "$registry_load_balancer_ip" ]] ||
	fail "registry LoadBalancer has address ${assigned_load_balancer_ip:-none}, expected ${registry_load_balancer_ip}"
assigned_node_port="$(
	"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" \
		-n "$REGISTRY_NAMESPACE" get service "$REGISTRY_NAME" \
		-o jsonpath='{.spec.ports[0].nodePort}'
)"
[[ -z "$assigned_node_port" || "$assigned_node_port" == "0" ]] ||
	fail "registry LoadBalancer unexpectedly allocated NodePort ${assigned_node_port}"

registry_address="${registry_load_balancer_ip}:${REGISTRY_PORT}"
registry_endpoint="https://${registry_address}"
registry_config_dir="/etc/containerd/certs.d/${registry_address}"
for node in "${nodes[@]}"; do
	"$CONTAINER_TOOL" exec "$node" mkdir -p "$registry_config_dir"
	"$CONTAINER_TOOL" exec -i "$node" tee "${registry_config_dir}/ca.crt" \
		<"$registry_ca_cert" >/dev/null
	printf 'server = "%s"\n\n[host."%s"]\n  capabilities = ["pull", "resolve", "push"]\n  ca = "%s/ca.crt"\n' \
		"$registry_endpoint" "$registry_endpoint" "$registry_config_dir" |
		"$CONTAINER_TOOL" exec -i "$node" tee "${registry_config_dir}/hosts.toml" >/dev/null

	reachable=0
	for ((attempt = 1; attempt <= 20; attempt++)); do
		if "$CONTAINER_TOOL" exec "$node" curl --noproxy '*' \
			--cacert "${registry_config_dir}/ca.crt" --fail --silent --show-error \
			--connect-timeout 2 --max-time 3 "${registry_endpoint}/v2/" >/dev/null 2>&1; then
			reachable=1
			break
		fi
		sleep 1
	done
	((reachable)) || fail "Kind node ${node} cannot reach registry LoadBalancer ${registry_endpoint}"
done

echo "deploy/kube: installing rootless BuildKit inside the cluster"
"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: ${BUILDKIT_NAMESPACE}
EOF
"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" \
	-n "$BUILDKIT_NAMESPACE" create configmap "${BUILDKIT_NAME}-registry-ca" \
	--from-file="ca.crt=${registry_ca_cert}" \
	--dry-run=client -o yaml |
	"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" \
		-n "$BUILDKIT_NAMESPACE" apply -f -

"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: ${BUILDKIT_NAME}-config
  namespace: ${BUILDKIT_NAMESPACE}
data:
  buildkitd.toml: |
    [registry."${registry_address}"]
      ca = ["/etc/buildkit/registry-ca/ca.crt"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ${BUILDKIT_NAME}
  namespace: ${BUILDKIT_NAMESPACE}
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app.kubernetes.io/name: ${BUILDKIT_NAME}
  template:
    metadata:
      annotations:
        checksum/registry-tls: "${registry_tls_fingerprint}"
      labels:
        app.kubernetes.io/name: ${BUILDKIT_NAME}
    spec:
      automountServiceAccountToken: false
      securityContext:
        fsGroup: 1000
      containers:
      - name: ${BUILDKIT_NAME}
        image: ${BUILDKIT_IMAGE}
        imagePullPolicy: IfNotPresent
        args:
        - --config=/etc/buildkit/buildkitd.toml
        - --oci-worker-no-process-sandbox
        readinessProbe:
          exec:
            command: ["buildctl", "debug", "workers"]
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          exec:
            command: ["buildctl", "debug", "workers"]
          initialDelaySeconds: 10
          periodSeconds: 30
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
        securityContext:
          runAsUser: 1000
          runAsGroup: 1000
          seccompProfile:
            type: Unconfined
          appArmorProfile:
            type: Unconfined
        volumeMounts:
        - name: state
          mountPath: /home/user/.local/share/buildkit
        - name: config
          mountPath: /etc/buildkit/buildkitd.toml
          subPath: buildkitd.toml
          readOnly: true
        - name: registry-ca
          mountPath: /etc/buildkit/registry-ca
          readOnly: true
      volumes:
      - name: state
        emptyDir: {}
      - name: config
        configMap:
          name: ${BUILDKIT_NAME}-config
      - name: registry-ca
        configMap:
          name: ${BUILDKIT_NAME}-registry-ca
EOF

"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" \
	-n "$BUILDKIT_NAMESPACE" rollout status \
	--timeout=5m "deployment/${BUILDKIT_NAME}"

"$KUBECTL" --kubeconfig "$KUBECONFIG" --context "$CONTEXT" apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: local-registry-hosting
  namespace: kube-public
data:
  localRegistryHosting.v1: |
    host: "${registry_address}"
    hostFromClusterNetwork: "${registry_address}"
    help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF

echo
echo "deploy/kube: cluster ${KIND_CLUSTER_NAME} is ready"
echo "deploy/kube: kubeconfig: ${KUBECONFIG}"
echo "deploy/kube: API server: https://${KIND_API_SERVER_ADDRESS}:${KIND_API_SERVER_PORT}"
echo "deploy/kube: registry runs as ${REGISTRY_NAMESPACE}/${REGISTRY_NAME}"
echo "deploy/kube: registry LoadBalancer: ${registry_address} (TLS verified from every Kind node)"
echo "deploy/kube: Grafana LoadBalancer: ${grafana_load_balancer_ip}:80 (reserved for make deploy)"
echo "deploy/kube: registry CA: ${registry_ca_cert}"
echo "deploy/kube: BuildKit: ${BUILDKIT_NAMESPACE}/${BUILDKIT_NAME} (${BUILDKIT_IMAGE})"
echo "deploy/kube: make push builds inside Kind and pushes directly"
echo "deploy/kube: pull from workloads with image ${registry_address}/NAME:TAG"
