diff --git a/resources/charts/namespaces/values.yaml b/resources/charts/namespaces/values.yaml index 26012ca48..9122ed82d 100644 --- a/resources/charts/namespaces/values.yaml +++ b/resources/charts/namespaces/values.yaml @@ -29,7 +29,7 @@ roles: resources: ["pods", "services"] verbs: ["get", "list", "watch", "create", "delete", "update"] - apiGroups: [""] - resources: ["pods"] + resources: ["pods", "secrets"] verbs: ["get", "list", "watch", "create", "delete", "update", "patch"] - apiGroups: [""] resources: ["pods/log", "pods/exec", "pods/attach", "pods/portforward"] diff --git a/src/warnet/control.py b/src/warnet/control.py index c604b3fcb..2c3bff45c 100644 --- a/src/warnet/control.py +++ b/src/warnet/control.py @@ -28,7 +28,9 @@ ) from .k8s import ( can_delete_pods, + delete_persistent_volume_claim, delete_pod, + delete_release_secrets, get_default_namespace, get_default_namespace_or, get_mission, @@ -149,30 +151,17 @@ def stop_all_scenarios(scenarios) -> None: @click.command() def down(): - """Bring down a running warnet quickly""" - - def uninstall_release(namespace, release_name): - cmd = f"helm uninstall {release_name} --namespace {namespace} --wait" - print(f"Initiating uninstall of {release_name} in namespace {namespace}") - subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - return f"Uninstalled {release_name} in namespace {namespace}" - - def delete_pod(pod_name, namespace): - cmd = f"kubectl delete pod --ignore-not-found=true {pod_name} -n {namespace} --wait" - print(f"Initiated deletion of pod: {pod_name} in namespace {namespace}") - subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - return f"Deleted pod: {pod_name} in namespace {namespace}" - - def delete_persistent_volume_claim(pvc_name, namespace): - cmd = f"kubectl delete pvc --ignore-not-found=true {pvc_name} -n {namespace} --wait" - print(f"Initiated deletion of PVC: {pvc_name} in namespace {namespace}") - subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - return f"Deleted PVC: {pvc_name} in namespace {namespace}" + """Bring down a running warnet carefully""" if not can_delete_pods(): click.secho("You do not have permission to bring down the network.", fg="red") return + def uninstall_release(namespace, release_name): + cmd = f"helm uninstall {release_name} --namespace {namespace} --wait" + print(f"Initiating uninstall of {release_name} in namespace {namespace}") + return subprocess.run(cmd, shell=True, capture_output=True, text=True) + namespaces = get_namespaces() release_list: list[dict[str, str]] = [] pvc_list: list[dict[str, str]] = [] @@ -261,7 +250,11 @@ def delete_persistent_volume_claim(pvc_name, namespace): # Wait for all tasks to complete and print results for future in as_completed(futures): - console.print(f"[yellow]{future.result()}[/yellow]") + result = future.result() + if result.returncode == 0: + console.print(f"[yellow]{result.stdout[:-1]}[/yellow]") + else: + console.print(f"[red]{result.stderr[:-1]}\n\tcmd: {result.args}[/red]") with ThreadPoolExecutor(max_workers=10) as executor: futures = [] @@ -278,9 +271,17 @@ def delete_persistent_volume_claim(pvc_name, namespace): executor.submit(delete_persistent_volume_claim, pvc["name"], pvc["namespace"]) ) + # Clean up Helm release secrets + for release in release_list: + futures.append( + executor.submit(delete_release_secrets, release["name"], release["namespace"]) + ) + # Wait for all tasks to complete and print results for future in as_completed(futures): - console.print(f"[yellow]{future.result()}[/yellow]") + e = future.exception() + if e and e.status != 404: + console.print(f"[red]{e}[/red]") console.print("[bold yellow]Teardown process initiated for all components.[/bold yellow]") console.print("[bold yellow]Note: Some processes may continue in the background.[/bold yellow]") diff --git a/src/warnet/k8s.py b/src/warnet/k8s.py index 615b65cb3..c28a082c0 100644 --- a/src/warnet/k8s.py +++ b/src/warnet/k8s.py @@ -10,7 +10,13 @@ import yaml from kubernetes import client, config, watch from kubernetes.client import CoreV1Api -from kubernetes.client.models import V1Namespace, V1Pod, V1PodList, V1TokenRequestSpec +from kubernetes.client.models import ( + V1DeleteOptions, + V1Namespace, + V1Pod, + V1PodList, + V1TokenRequestSpec, +) from kubernetes.client.rest import ApiException from kubernetes.dynamic import DynamicClient from kubernetes.stream import stream @@ -150,14 +156,41 @@ def apply_kubernetes_yaml_obj(yaml_obj: str) -> None: def delete_namespace(namespace: str) -> bool: - command = f"kubectl delete namespace {namespace} --ignore-not-found" - return run_command(command) + sclient = get_static_client() + sclient.sclient.delete_namespace( + name=namespace, + body=V1DeleteOptions, + ) + print(f"Deleted namespace {namespace}") -def delete_pod(pod_name: str, namespace: Optional[str] = None) -> bool: +def delete_pod(pod_name: str, namespace: Optional[str] = None): namespace = get_default_namespace_or(namespace) - command = f"kubectl -n {namespace} delete pod {pod_name}" - return stream_command(command) + sclient = get_static_client() + sclient.delete_namespaced_pod(name=pod_name, namespace=namespace, body=V1DeleteOptions()) + print(f"Deleted pod {pod_name}.{namespace}") + + +def delete_release_secrets(release_name: str, namespace: Optional[str] = None): + namespace = get_default_namespace_or(namespace) + sclient = get_static_client() + secrets = sclient.list_namespaced_secret( + namespace=namespace, label_selector=f"owner=helm,name={release_name}" + ) + for secret in secrets.items: + sclient.delete_namespaced_secret( + name=secret.metadata.name, namespace=namespace, body=V1DeleteOptions() + ) + print(f"Deleted secret {release_name}.{namespace}") + + +def delete_persistent_volume_claim(pvc_name: str, namespace: Optional[str] = None): + namespace = get_default_namespace_or(namespace) + sclient = get_static_client() + sclient.delete_namespaced_persistent_volume_claim( + name=pvc_name, namespace=namespace, body=V1DeleteOptions() + ) + print(f"Deleted PVC {pvc_name}.{namespace}") def get_default_namespace() -> str: