Skip to content

Commit 24a563e

Browse files
AFDudleyclaude
andcommitted
Squashed 'stack-orchestrator/' changes from cerc-io/main
Merge upstream changes: namespace param for containers_in_pod, mount root dedup, compose port mapping. Preserve imagePullPolicy=Always. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2 parents 67cc2d5 + 268ff74 commit 24a563e

5 files changed

Lines changed: 124 additions & 8 deletions

File tree

stack_orchestrator/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,4 @@
4545
high_memlock_runtime = "high-memlock"
4646
high_memlock_spec_filename = "high-memlock-spec.json"
4747
acme_email_key = "acme-email"
48+
kind_mount_root_key = "kind-mount-root"

stack_orchestrator/deploy/k8s/cluster_info.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,11 @@ def get_pvs(self):
355355

356356
if self.spec.is_kind_deployment():
357357
host_path = client.V1HostPathVolumeSource(
358-
path=get_kind_pv_bind_mount_path(volume_name)
358+
path=get_kind_pv_bind_mount_path(
359+
volume_name,
360+
kind_mount_root=self.spec.get_kind_mount_root(),
361+
host_path=volume_path,
362+
)
359363
)
360364
else:
361365
host_path = client.V1HostPathVolumeSource(path=volume_path)

stack_orchestrator/deploy/k8s/helpers.py

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -476,10 +476,12 @@ def local_registry_image(image: str) -> str:
476476
return f"localhost:{LOCAL_REGISTRY_HOST_PORT}/{image}"
477477

478478

479-
def pods_in_deployment(core_api: client.CoreV1Api, deployment_name: str):
479+
def pods_in_deployment(
480+
core_api: client.CoreV1Api, deployment_name: str, namespace: str = "default"
481+
):
480482
pods = []
481483
pod_response = core_api.list_namespaced_pod(
482-
namespace="default", label_selector=f"app={deployment_name}"
484+
namespace=namespace, label_selector=f"app={deployment_name}"
483485
)
484486
if opts.o.debug:
485487
print(f"pod_response: {pod_response}")
@@ -489,9 +491,14 @@ def pods_in_deployment(core_api: client.CoreV1Api, deployment_name: str):
489491
return pods
490492

491493

492-
def containers_in_pod(core_api: client.CoreV1Api, pod_name: str) -> list[str]:
494+
def containers_in_pod(
495+
core_api: client.CoreV1Api, pod_name: str, namespace: str = "default"
496+
) -> list[str]:
493497
containers: list[str] = []
494-
pod_response = cast(client.V1Pod, core_api.read_namespaced_pod(pod_name, namespace="default"))
498+
pod_response = cast(
499+
client.V1Pod,
500+
core_api.read_namespaced_pod(pod_name, namespace=namespace),
501+
)
495502
if opts.o.debug:
496503
print(f"pod_response: {pod_response}")
497504
if not pod_response.spec or not pod_response.spec.containers:
@@ -521,7 +528,14 @@ def named_volumes_from_pod_files(parsed_pod_files):
521528
return named_volumes
522529

523530

524-
def get_kind_pv_bind_mount_path(volume_name: str):
531+
def get_kind_pv_bind_mount_path(
532+
volume_name: str,
533+
kind_mount_root: str | None = None,
534+
host_path: str | None = None,
535+
):
536+
if kind_mount_root and host_path and host_path.startswith(kind_mount_root):
537+
rel = os.path.relpath(host_path, kind_mount_root)
538+
return f"/mnt/{rel}"
525539
return f"/mnt/{volume_name}"
526540

527541

@@ -634,6 +648,7 @@ def _generate_kind_mounts(parsed_pod_files, deployment_dir, deployment_context):
634648
volume_definitions = []
635649
volume_host_path_map = _get_host_paths_for_volumes(deployment_context)
636650
seen_host_path_mounts = set() # Track to avoid duplicate mounts
651+
kind_mount_root = deployment_context.spec.get_kind_mount_root()
637652

638653
# Cluster state backup for offline data recovery (unique per deployment)
639654
# etcd contains all k8s state; PKI certs needed to decrypt etcd offline
@@ -654,6 +669,14 @@ def _generate_kind_mounts(parsed_pod_files, deployment_dir, deployment_context):
654669
f" propagation: HostToContainer\n"
655670
)
656671

672+
# When kind-mount-root is set, emit a single extraMount for the root.
673+
# Individual volumes whose host path starts with the root are covered
674+
# by this single mount and don't need their own extraMount entries.
675+
mount_root_emitted = False
676+
if kind_mount_root:
677+
volume_definitions.append(f" - hostPath: {kind_mount_root}\n" f" containerPath: /mnt\n")
678+
mount_root_emitted = True
679+
657680
# Note these paths are relative to the location of the pod files (at present)
658681
# So we need to fix up to make them correct and absolute because kind assumes
659682
# relative to the cwd.
@@ -705,6 +728,11 @@ def _generate_kind_mounts(parsed_pod_files, deployment_dir, deployment_context):
705728
volume_host_path_map[volume_name],
706729
deployment_dir,
707730
)
731+
# Skip if covered by mount root
732+
if mount_root_emitted and str(host_path).startswith(
733+
kind_mount_root
734+
):
735+
continue
708736
container_path = get_kind_pv_bind_mount_path(volume_name)
709737
volume_definitions.append(
710738
f" - hostPath: {host_path}\n"
@@ -744,9 +772,35 @@ def _generate_kind_port_mappings_from_services(parsed_pod_files):
744772

745773
def _generate_kind_port_mappings(parsed_pod_files):
746774
port_definitions = []
775+
seen = set()
747776
# Map port 80 and 443 for the Caddy ingress controller (HTTPS support)
748777
for port_string in ["80", "443"]:
749-
port_definitions.append(f" - containerPort: {port_string}\n hostPort: {port_string}\n")
778+
port_definitions.append(
779+
f" - containerPort: {port_string}\n" f" hostPort: {port_string}\n"
780+
)
781+
seen.add((port_string, "TCP"))
782+
# Map ports declared in compose services
783+
for pod in parsed_pod_files:
784+
parsed_pod_file = parsed_pod_files[pod]
785+
if "services" in parsed_pod_file:
786+
for service_name in parsed_pod_file["services"]:
787+
service_obj = parsed_pod_file["services"][service_name]
788+
for port_entry in service_obj.get("ports", []):
789+
port_str = str(port_entry)
790+
protocol = "TCP"
791+
if "/" in port_str:
792+
port_str, proto = port_str.split("/", 1)
793+
protocol = proto.upper()
794+
if ":" in port_str:
795+
port_str = port_str.split(":")[-1]
796+
port_num = port_str.strip("'\"")
797+
if (port_num, protocol) not in seen:
798+
seen.add((port_num, protocol))
799+
port_definitions.append(
800+
f" - containerPort: {port_num}\n"
801+
f" hostPort: {port_num}\n"
802+
f" protocol: {protocol}\n"
803+
)
750804
return (
751805
""
752806
if len(port_definitions) == 0
@@ -950,7 +1004,8 @@ def _generate_containerd_config_patches(deployment_dir: Path, has_high_memlock:
9501004
patches = []
9511005

9521006
# Always configure the local registry mirror so kind nodes pull from it
953-
registry_plugin = f'plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:{LOCAL_REGISTRY_HOST_PORT}"'
1007+
mirror = f"localhost:{LOCAL_REGISTRY_HOST_PORT}"
1008+
registry_plugin = 'plugins."io.containerd.grpc.v1.cri"' f'.registry.mirrors."{mirror}"'
9541009
endpoint = f"http://{LOCAL_REGISTRY_NAME}:{LOCAL_REGISTRY_CONTAINER_PORT}"
9551010
patches.append(f" [{registry_plugin}]\n" f' endpoint = ["{endpoint}"]')
9561011

stack_orchestrator/deploy/spec.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,5 +209,8 @@ def is_kubernetes_deployment(self):
209209
def is_kind_deployment(self):
210210
return self.get_deployment_type() in [constants.k8s_kind_deploy_type]
211211

212+
def get_kind_mount_root(self):
213+
return self.obj.get(constants.kind_mount_root_key)
214+
212215
def is_docker_deployment(self):
213216
return self.get_deployment_type() in [constants.compose_deploy_type]

tests/scripts/run-test-local.sh

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/bin/bash
2+
# Run a test suite locally in an isolated venv.
3+
#
4+
# Usage:
5+
# ./tests/scripts/run-test-local.sh <test-script>
6+
#
7+
# Examples:
8+
# ./tests/scripts/run-test-local.sh tests/webapp-test/run-webapp-test.sh
9+
# ./tests/scripts/run-test-local.sh tests/smoke-test/run-smoke-test.sh
10+
# ./tests/scripts/run-test-local.sh tests/k8s-deploy/run-deploy-test.sh
11+
#
12+
# The script creates a temporary venv, installs shiv, builds the laconic-so
13+
# package, runs the requested test, then cleans up.
14+
15+
set -euo pipefail
16+
17+
if [ $# -lt 1 ]; then
18+
echo "Usage: $0 <test-script> [args...]"
19+
exit 1
20+
fi
21+
22+
TEST_SCRIPT="$1"
23+
shift
24+
25+
if [ ! -f "$TEST_SCRIPT" ]; then
26+
echo "Error: $TEST_SCRIPT not found"
27+
exit 1
28+
fi
29+
30+
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
31+
VENV_DIR=$(mktemp -d /tmp/so-test-XXXXXX)
32+
33+
cleanup() {
34+
echo "Cleaning up venv: $VENV_DIR"
35+
rm -rf "$VENV_DIR"
36+
}
37+
trap cleanup EXIT
38+
39+
cd "$REPO_DIR"
40+
41+
echo "==> Creating venv in $VENV_DIR"
42+
python3 -m venv "$VENV_DIR"
43+
source "$VENV_DIR/bin/activate"
44+
45+
echo "==> Installing shiv"
46+
pip install -q shiv
47+
48+
echo "==> Building laconic-so package"
49+
./scripts/create_build_tag_file.sh
50+
./scripts/build_shiv_package.sh
51+
52+
echo "==> Running: $TEST_SCRIPT $*"
53+
exec "./$TEST_SCRIPT" "$@"

0 commit comments

Comments
 (0)