Skip to content

Commit 5b1963e

Browse files
committed
fix: missing implementation
1 parent 3f1e6b8 commit 5b1963e

6 files changed

Lines changed: 69 additions & 28 deletions

File tree

pkg/resources/pod_test.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const (
1616
testSetupVenv = "setup-venv"
1717
testSSHDContainer = "sshd"
1818
testSSHFSName = "sshfs-0"
19+
testCWSidecarName = "cw-0"
20+
testSSHPubkeyName = "ssh-pubkey"
1921
)
2022

2123
func TestBuildPod_BasicConfig(t *testing.T) {
@@ -1163,8 +1165,8 @@ func TestBuildPod_WithCWMounts(t *testing.T) {
11631165

11641166
// Second container should be cw sidecar
11651167
cwSidecar := pod.Spec.Containers[1]
1166-
if cwSidecar.Name != "cw-0" {
1167-
t.Errorf("expected sidecar name 'cw-0', got '%s'", cwSidecar.Name)
1168+
if cwSidecar.Name != testCWSidecarName {
1169+
t.Errorf("expected sidecar name '%s', got '%s'", testCWSidecarName, cwSidecar.Name)
11681170
}
11691171
}
11701172

@@ -1208,8 +1210,8 @@ func TestExpandMounts_CW(t *testing.T) {
12081210
}
12091211

12101212
sidecar := sidecars[0]
1211-
if sidecar.Name != "cw-0" {
1212-
t.Errorf("expected name 'cw-0', got %q", sidecar.Name)
1213+
if sidecar.Name != testCWSidecarName {
1214+
t.Errorf("expected name '%s', got %q", testCWSidecarName, sidecar.Name)
12131215
}
12141216

12151217
if !strings.Contains(sidecar.Image, "s3fs") {
@@ -1335,7 +1337,7 @@ func TestBuildPod_MountPropagation_WithFUSESidecar(t *testing.T) {
13351337
if pod.Spec.Containers[i].Name == testMarimoContainer {
13361338
marimoContainer = &pod.Spec.Containers[i]
13371339
}
1338-
if pod.Spec.Containers[i].Name == "cw-0" {
1340+
if pod.Spec.Containers[i].Name == testCWSidecarName {
13391341
cwContainer = &pod.Spec.Containers[i]
13401342
}
13411343
}
@@ -1344,7 +1346,7 @@ func TestBuildPod_MountPropagation_WithFUSESidecar(t *testing.T) {
13441346
t.Fatal("marimo container not found")
13451347
}
13461348
if cwContainer == nil {
1347-
t.Fatal("cw-0 container not found")
1349+
t.Fatalf("%s container not found", testCWSidecarName)
13481350
}
13491351

13501352
// Check marimo has HostToContainer propagation on PVC mount
@@ -1442,16 +1444,16 @@ func TestBuildPod_SSHFSSidecar_SecretMount(t *testing.T) {
14421444
// Check ssh-pubkey volume exists
14431445
var foundSSHPubkeyVolume bool
14441446
for _, vol := range pod.Spec.Volumes {
1445-
if vol.Name == "ssh-pubkey" {
1446-
if vol.Secret == nil || vol.Secret.SecretName != "ssh-pubkey" {
1447-
t.Error("ssh-pubkey volume should reference ssh-pubkey secret")
1447+
if vol.Name == testSSHPubkeyName {
1448+
if vol.Secret == nil || vol.Secret.SecretName != testSSHPubkeyName {
1449+
t.Errorf("%s volume should reference %s secret", testSSHPubkeyName, testSSHPubkeyName)
14481450
}
14491451
foundSSHPubkeyVolume = true
14501452
break
14511453
}
14521454
}
14531455
if !foundSSHPubkeyVolume {
1454-
t.Error("expected ssh-pubkey volume to be present for sshfs sidecar")
1456+
t.Errorf("expected %s volume to be present for sshfs sidecar", testSSHPubkeyName)
14551457
}
14561458

14571459
// Find sshfs sidecar and check it has the secret mounted
@@ -1470,7 +1472,7 @@ func TestBuildPod_SSHFSSidecar_SecretMount(t *testing.T) {
14701472
// Check ssh-pubkey is mounted at /config/ssh-pubkey
14711473
var foundSSHPubkeyMount bool
14721474
for _, vm := range sshfsSidecar.VolumeMounts {
1473-
if vm.Name == "ssh-pubkey" && vm.MountPath == "/config/ssh-pubkey" && vm.ReadOnly {
1475+
if vm.Name == testSSHPubkeyName && vm.MountPath == "/config/"+testSSHPubkeyName && vm.ReadOnly {
14741476
foundSSHPubkeyMount = true
14751477
break
14761478
}
@@ -1500,8 +1502,8 @@ func TestBuildPod_NoSSHFSSidecar_NoSecretMount(t *testing.T) {
15001502

15011503
// Check ssh-pubkey volume does NOT exist
15021504
for _, vol := range pod.Spec.Volumes {
1503-
if vol.Name == "ssh-pubkey" {
1504-
t.Error("ssh-pubkey volume should NOT be present when no sshfs sidecar")
1505+
if vol.Name == testSSHPubkeyName {
1506+
t.Errorf("%s volume should NOT be present when no sshfs sidecar", testSSHPubkeyName)
15051507
}
15061508
}
15071509
}

plugin/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ kubectl marimo delete [OPTIONS] FILE
106106

107107
Options:
108108
- `-n, --namespace` - Kubernetes namespace
109-
- `--keep-pvc` - Preserve persistent storage
109+
- `--delete-pvc` - Also delete PersistentVolumeClaim (PVC is preserved by default)
110110
- `--no-sync` - Delete without syncing changes back
111111

112112
### status

plugin/examples/with-rsync.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313

1414
@app.cell
1515
def check_sync():
16-
import os
17-
import marimo as mo
1816

1917
return
2018

plugin/kubectl_marimo/delete.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import click
77

88
from .formats import parse_file
9-
from .k8s import delete_resource, exec_in_pod
9+
from .k8s import delete_resource, exec_in_pod, patch_resource
1010
from .resources import compute_hash, resource_name, detect_content_type
1111
from .swap import read_swap_file, delete_swap_file
1212
from .sync import sync_local_mounts
@@ -17,7 +17,7 @@ def delete_notebook(
1717
namespace: str | None = None,
1818
force: bool = False,
1919
no_sync: bool = False,
20-
keep_pvc: bool = False,
20+
delete_pvc: bool = False,
2121
) -> None:
2222
"""Delete notebook deployment from cluster."""
2323
path = Path(file_path)
@@ -77,14 +77,19 @@ def delete_notebook(
7777
if meta.local_mounts:
7878
sync_local_mounts(meta.name, namespace, meta.local_mounts)
7979

80-
# Delete the MarimoNotebook resource
81-
# Note: PVC is deleted via owner reference unless keep_pvc is set
82-
if keep_pvc:
83-
click.echo("Note: --keep-pvc requires manual PVC deletion prevention")
84-
click.echo(
85-
f' kubectl patch pvc -n {namespace} {name}-pvc -p \'{{"metadata":{{"ownerReferences":[]}}}}\''
86-
)
80+
# By default, preserve PVC by removing owner references before delete
81+
# With --delete-pvc, skip patching so PVC is garbage collected
82+
if not delete_pvc:
83+
pvc_name = f"{name}-pvc"
84+
patch_json = '{"metadata":{"ownerReferences":null}}'
85+
if not patch_resource("pvc", pvc_name, namespace, patch_json):
86+
click.echo(
87+
"Warning: Could not patch PVC to remove owner references. "
88+
"PVC may be deleted with the notebook.",
89+
err=True,
90+
)
8791

92+
# Delete the MarimoNotebook resource
8893
if not delete_resource("marimos.marimo.io", name, namespace):
8994
sys.exit(1)
9095

plugin/kubectl_marimo/k8s.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,39 @@ def get_pod_logs(pod_name: str, namespace: str) -> tuple[bool, str]:
100100
return False, "kubectl not found in PATH"
101101

102102

103+
def patch_resource(
104+
kind: str,
105+
name: str,
106+
namespace: str,
107+
patch: str,
108+
) -> bool:
109+
"""Patch a Kubernetes resource using kubectl.
110+
111+
Returns True on success, False on failure.
112+
"""
113+
cmd = [
114+
"kubectl",
115+
"patch",
116+
kind,
117+
name,
118+
"-n",
119+
namespace,
120+
"--type=merge",
121+
"-p",
122+
patch,
123+
]
124+
try:
125+
result = subprocess.run(cmd, capture_output=True, text=True)
126+
if result.returncode != 0:
127+
print(f"Error: {result.stderr}", file=sys.stderr)
128+
return False
129+
print(result.stdout, end="")
130+
return True
131+
except FileNotFoundError:
132+
print("Error: kubectl not found in PATH", file=sys.stderr)
133+
return False
134+
135+
103136
def get_resource(
104137
kind: str,
105138
name: str,

plugin/kubectl_marimo/main.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,18 @@ def sync(file: str, namespace: str | None, force: bool):
108108
"-n", "--namespace", help="Kubernetes namespace (default: from swap file)"
109109
)
110110
@click.option(
111-
"--keep-pvc", is_flag=True, help="Keep PersistentVolumeClaim (preserve data)"
111+
"--delete-pvc",
112+
is_flag=True,
113+
help="Also delete PersistentVolumeClaim (destroys data)",
112114
)
113115
@click.option("--no-sync", is_flag=True, help="Delete without syncing changes back")
114-
def delete(file: str, namespace: str | None, keep_pvc: bool, no_sync: bool):
116+
def delete(file: str, namespace: str | None, delete_pvc: bool, no_sync: bool):
115117
"""Sync changes, then delete cluster resources.
116118
117119
FILE is the local notebook that was previously deployed.
120+
PVC is preserved by default to protect your data.
118121
"""
119-
delete_notebook(file, namespace=namespace, keep_pvc=keep_pvc, no_sync=no_sync)
122+
delete_notebook(file, namespace=namespace, delete_pvc=delete_pvc, no_sync=no_sync)
120123

121124

122125
@cli.command()

0 commit comments

Comments
 (0)