Skip to content

Commit f11328a

Browse files
EliShteinmanclaude
andcommitted
fix(cli-proxy): parse quoted args, raw RESP replies, threaded workers
Three related reliability fixes for the CLI playground proxy, all visible in the docs site interactive examples and in OpenShift health probes. 1. Quoted-arg parsing (shlex). Naive str.split() did not respect quotes, so `SET k "with spaces" EX 60` was tokenized into 9 parts and Redis returned a syntax error. Replace with shlex.split() which matches the real redis-cli quoting rules; surface ValueError as a normal reply rather than letting it 500. 2. Bypass redis-py response_callbacks. The library translates RESP-level replies into Python types per command — `EXPIRE`/ `SETNX`/`PERSIST` become bool, `SET` becomes True/None, `HGETALL` becomes dict. The JS frontend (`static/js/cli.js` formatReply) expects RESP-shaped values: string, int, list, null. Clearing `client.response_callbacks` makes redis-py return raw values, so EXPIRE renders as `(integer) 1`/`(integer) 0` instead of mis-mapped `"OK"`/`"(nil)"`, and HGETALL renders as a flat list like redis-cli. normalize_result simplified accordingly; the PING special-case is no longer needed. 3. gthread workers. The Dockerfile used the default sync worker class with --workers 2, so two concurrent /cli requests fully blocked /healthz, occasionally tripping the kubelet liveness/ readiness probes (context deadline exceeded). Switch to --worker-class gthread --threads 4: same 2 processes, 8 concurrent slots, /healthz never queues behind redis work. Also bump timeoutSeconds to 3 on both probes for safety margin. Image rebuilt + pushed as a0533057932/redis-docs-cli with tags `0.3.3` and `latest` (manifest digest sha256:e22b87bcfe791b, multi-arch amd64+arm64). Helm chart bumped 1.5.4 -> 1.5.6; README cli tag refs updated to 0.3.3 in en and he variants. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7636532 commit f11328a

6 files changed

Lines changed: 24 additions & 24 deletions

File tree

helm/cli-proxy/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ USER appuser
1212

1313
EXPOSE 8090
1414

15-
CMD ["gunicorn", "--bind", "0.0.0.0:8090", "--workers", "2", "--control-socket", "/tmp/gunicorn.ctl", "main:app"]
15+
CMD ["gunicorn", "--bind", "0.0.0.0:8090", "--workers", "2", "--worker-class", "gthread", "--threads", "4", "--control-socket", "/tmp/gunicorn.ctl", "main:app"]

helm/cli-proxy/main.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import json
21
import os
2+
import shlex
33
import uuid
44

55
import redis
@@ -17,23 +17,16 @@
1717

1818

1919
def normalize_result(result):
20-
"""Convert redis-py Python types to RESP-like values for the CLI frontend."""
21-
if result is True:
22-
return "OK"
23-
if result is False:
24-
return "(nil)"
2520
if isinstance(result, bytes):
2621
return result.decode("utf-8", errors="replace")
22+
if isinstance(result, (list, tuple, set)):
23+
return [normalize_result(item) for item in result]
2724
if isinstance(result, dict):
2825
flat = []
2926
for k, v in result.items():
3027
flat.append(normalize_result(k))
3128
flat.append(normalize_result(v))
3229
return flat
33-
if isinstance(result, (list, tuple)):
34-
return [normalize_result(item) for item in result]
35-
if isinstance(result, set):
36-
return [normalize_result(item) for item in result]
3730
return result
3831

3932

@@ -43,6 +36,10 @@ def get_client(session_id: str | None) -> tuple[redis.Redis, str]:
4336

4437
sid = session_id or str(uuid.uuid4())
4538
client = redis.Redis(connection_pool=pool)
39+
# Bypass redis-py response_callbacks so the JS frontend (formatReply in
40+
# static/js/cli.js) receives raw RESP-shaped values: 'OK' instead of True,
41+
# int 1/0 instead of bool for EXPIRE/SETNX/PERSIST, None instead of nil.
42+
client.response_callbacks = {}
4643
sessions[sid] = client
4744
return client, sid
4845

@@ -62,15 +59,16 @@ def cli():
6259
replies = []
6360

6461
for cmd in commands:
65-
parts = cmd.split()
62+
try:
63+
parts = shlex.split(cmd)
64+
except ValueError as e:
65+
replies.append({"error": True, "value": f"parse error: {e}"})
66+
continue
6667
if not parts:
6768
replies.append({"error": True, "value": "empty command"})
6869
continue
6970
try:
70-
cmd_upper = parts[0].upper()
7171
result = client.execute_command(*parts)
72-
if cmd_upper == "PING" and result is True:
73-
result = "PONG"
7472
replies.append({"error": False, "value": normalize_result(result)})
7573
except redis.RedisError as e:
7674
replies.append({"error": True, "value": str(e)})

helm/redis-docs/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ description: |
77
substitution for AI/RAG outputs, and optional Prometheus metrics
88
and CLI / Jupyter playground sidecars.
99
type: application
10-
version: 1.5.4
10+
version: 1.5.6
1111
appVersion: "e25a0847d"
1212
icon: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNiIgaGVpZ2h0PSIyMyIgdmlld0JveD0iMCAwIDI2IDIzIj4KICAgIDxnIGZpbGw9Im5vbmUiPgogICAgICAgIDxwYXRoIGZpbGw9IiNGRkYiIHN0cm9rZT0iI0ZGRiIgc3Ryb2tlLXdpZHRoPSIuNjY1IiBkPSJNMTQuMDM3LjAyYy0xLjM1LS40OTctMS44OTctLjQ4Ni0zLjQ4NC4wODctMS4zNTcuNDktNy4zNCAyLjg0LTkuNzU0IDMuNzg5LS4zODYuMTUxLS42NjkuMjk0LTEuMTMuOTIzdjIuNTM0YzAgLjIyNi4xOC40ODcuNjUuODk3LS4xOTcuMTM2LS40LjMzNy0uNjYuNjlsLjU5NCAzLjMyMWMtLjE3Ny4xMzUtLjM2My4zMy0uNTk0LjY0NmwuMjU5IDMuMDQ5Yy4xNjkuMTc1LjQ3My4zNjcuOTEuNTc1LjY1OC4zMTYgMi44NTcgMS4yMzYgNC45ODQgMi4xMjUgMS45ODIuODMgMy45MDIgMS42MzIgNC40NCAxLjg5IDEuMjE2LjU4MyAyLjA1Ny42MTggMy42MDgtLjIuNzMtLjM4NiAyLjgzNy0xLjI5NSA0Ljg4MS0yLjE3OSAxLjk0LS44MzggMy44MjMtMS42NTIgNC40NDUtMS45OC4zODYtLjIwMi42Ni0uMzczIDEuMTE3LTEuMDNMMjQuMyAxMi41OWMtLjAxNS0uMjEzLS4xODItLjQ0Ny0uNi0uNjk2LjE4LS4xNTMuMzctLjM2Ni42MDktLjcxMmwtLjIwMS0yLjk3M2MtLjA5My0uMTEyLS4yNDctLjIzNC0uNDYyLS4zODIuMTk4LS4xNTcuNC0uMzc3LjY2NS0uNzU5bC0uMjc4LTMuMDI4Yy0uMTY3LS4xNTMtLjQ2OC0uMzA4LS45MDMtLjQ2OC0uNjI1LS4yMy0yLjU5NS0xLjAwNi00LjU2MS0xLjc4MS0xLjk1NC0uNzctMy45MDQtMS41NC00LjUzMy0xLjc3MnoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEgMSkiLz4KICAgICAgICA8cGF0aCBmaWxsPSIjQTMyNDIyIiBkPSJNMjMuMDMxIDE1Ljg5NmMtMS4yNzguNjctNy45MDYgMy40MDUtOS4zMjYgNC4xNTYtMS40Mi43NS0yLjE5My43My0zLjMwOS4xOTVDOS4yOCAxOS43MSAyLjI0NCAxNi44NC45NzEgMTYuMjNjLS42MzYtLjMwMi0uOTctLjU2LS45Ny0uODAydi0yLjQzM3M5LjE4Mi0yIDEwLjY2NC0yLjUzOGMxLjQ4My0uNTM3IDEuOTk0LS41NTIgMy4yNTctLjA5IDEuMjYzLjQ2MyA4LjgwMyAxLjgzIDEwLjA0OCAyLjI4OHYyLjM5NWMwIC4yNS0uMjg2LjUtLjkzOS44NDN2LjAwMnoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEgMSkiLz4KICAgICAgICA8cGF0aCBmaWxsPSIjREMzODJDIiBkPSJNMjMuMDMxIDEzLjQ2NmMtMS4yNzguNjY3LTcuOTA2IDMuNDAzLTkuMzI2IDQuMTQtMS40Mi43MzgtMi4xOTMuNzMtMy4zMDkuMTk4QzkuMjggMTcuMjcgMi4yNDQgMTQuNDA5Ljk3MSAxMy44Yy0xLjI3NC0uNjA4LTEuMjk4LTEuMDMtLjA1LTEuNTJzOC4yNjItMy4yNSA5Ljc0NC0zLjc4NWMxLjQ4My0uNTM2IDEuOTk0LS41NTMgMy4yNTctLjA4OCAxLjI2My40NjUgNy44NDggMy4wOTUgOS4wOTQgMy41NTMgMS4yNDYuNDU3IDEuMjk2LjgzNS4wMTUgMS41di4wMDV6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxIDEpIi8+CiAgICAgICAgPHBhdGggZmlsbD0iI0EzMjQyMiIgZD0iTTIzLjAzMSAxMS45MzNjLTEuMjc4LjY2OC03LjkwNiAzLjQwMy05LjMyNiA0LjE0LTEuNDIuNzM4LTIuMTkzLjczLTMuMzA5LjE5OFMyLjI0NCAxMi44NzguOTcxIDEyLjI2OGMtLjYzNi0uMzA1LS45Ny0uNTYyLS45Ny0uODA1di0yLjQzczkuMTgyLTIgMTAuNjY0LTIuNTRjMS40ODMtLjU0IDEuOTk0LS41NTMgMy4yNTctLjA4OCAxLjI2My40NjUgOC44MDMgMS44MjggMTAuMDQ4IDIuMjg2djIuMzk3YzAgLjI1LS4yODYuNS0uOTM5Ljg0NXoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEgMSkiLz4KICAgICAgICA8cGF0aCBmaWxsPSIjREMzODJDIiBkPSJNMjMuMDMxIDkuNWMtMS4yNzguNjctNy45MDYgMy40MDMtOS4zMjYgNC4xNDEtMS40Mi43MzgtMi4xOTMuNzMzLTMuMzA5LjE5Ny0xLjExNi0uNTM1LTguMTUyLTMuMzkyLTkuNDI1LTQuMDAyLTEuMjc0LS42MS0xLjI5OC0xLjAyOC0uMDUtMS41MThzOC4yNjItMy4yNSA5Ljc0NC0zLjc4NWMxLjQ4My0uNTM1IDEuOTk0LS41NTMgMy4yNTctLjA5IDEuMjYzLjQ2MiA3Ljg0OCAzLjEwNyA5LjA5NCAzLjU1MiAxLjI0Ni40NDYgMS4yOTYuODM2LjAxNSAxLjV2LjAwNnoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEgMSkiLz4KICAgICAgICA8cGF0aCBmaWxsPSIjQTMyNDIyIiBkPSJNMjMuMDMxIDcuODJjLTEuMjc4LjY3LTcuOTA2IDMuNDA2LTkuMzI2IDQuMTQzLTEuNDIuNzM4LTIuMTkzLjczLTMuMzA5LjE5NUM5LjI4IDExLjYyMyAyLjI0NCA4Ljc2Ni45NzEgOC4xNTUuMzM1IDcuODUzIDAgNy41OTUgMCA3LjM1M1Y0LjkyczkuMTgyLTIgMTAuNjY0LTIuNTM4YzEuNDgzLS41MzcgMS45OTQtLjU1MiAzLjI1Ny0uMDkgMS4yNjMuNDYzIDguODAzIDEuODMgMTAuMDQ4IDIuMjg4djIuMzk4YzAgLjI1LS4yODYuNS0uOTM5Ljg0MnoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEgMSkiLz4KICAgICAgICA8cGF0aCBmaWxsPSIjREMzODJDIiBkPSJNMjMuMDMxIDUuMzljLTEuMjc4LjY2LTcuOTA2IDMuNDEtOS4zMjYgNC4xNC0xLjQyLjczLTIuMTkzLjczLTMuMzA5LjE5OFMyLjI0NCA2LjMzMy45NzEgNS43MjVjLTEuMjc0LS42MDctMS4yOTgtMS4wMy0uMDUtMS41MlM5LjE4My45NTUgMTAuNjY1LjQyYzEuNDgzLS41MzUgMS45OTQtLjU1MyAzLjI1Ny0uMDg4IDEuMjYzLjQ2NSA3Ljg0OCAzLjA5NiA5LjA5NCAzLjU1MyAxLjI0Ni40NTggMS4yOTYuODM1LjAxNSAxLjV2LjAwNXoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEgMSkiLz4KICAgICAgICA8cGF0aCBmaWxsPSIjRkZGIiBkPSJNMTIuMzk1IDEuODEyTDEzLjcxNCAxLjE5OSAxMy4zNTcgMi40MSAxNC42OTkgMy4xMjIgMTIuOTY5IDMuMzc4IDEyLjU4IDQuNzAxIDExLjk1NSAzLjIyNSA5Ljk1NSAyLjk2OSAxMS40NDggMi4yMDcgMTEgMS4wMzUgMTIuMzk1IDEuODEyIDEyLjM5NSAxLjgxMnpNMTEuNjg1IDguODlMOC4zNzQgNy4xNzggMTMuMTE3IDYuMjcyIDExLjY4NSA4Ljg5IDExLjY4NSA4Ljg5ek02Ljc5NCA0LjE3N2MxLjc0NiAwIDMuMTYxLjU4NyAzLjE2MSAxLjMxIDAgLjcyMi0xLjQyIDEuMzA5LTMuMTYgMS4zMDktMS43NDIgMC0zLjE2NS0uNTg3LTMuMTY1LTEuMzEgMC0uNzIyIDEuNDE4LTEuMzEgMy4xNjQtMS4zMXoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEgMSkiLz4KICAgICAgICA8cGF0aCBmaWxsPSIjNzQxMTEzIiBkPSJNMTYuODA3IDMuNjUzTDIwLjQ5NiA1LjIyNCAxNi44MSA2Ljc5NiAxNi44MDcgMy42NTMgMTYuODA3IDMuNjUzeiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMSAxKSIvPgogICAgICAgIDxwYXRoIGZpbGw9IiNBQzI3MjQiIGQ9Ik0xNy4zMzEgMy42NTNMMTcuMzM0IDYuNjM1IDE2Ljk3MyA2Ljc5NiAxMy42NDUgNS4zMDQgMTcuMzMxIDMuNjUzIDE3LjMzMSAzLjY1M3oiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEgMSkiLz4KICAgIDwvZz4KPC9zdmc+Cg==
1313
keywords:

helm/redis-docs/README-he.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ canonicalURL: "https://docs.intranet.example.com"
154154
| `a0533057932/redis-docs` | `<HASH>` / `latest` | 80 | הרצה רגילה עם `docker run` (privileged) | כן — אחד מהשניים |
155155
| `a0533057932/redis-docs` | `<HASH>-unprivileged` / `unprivileged` | 8080 | Kubernetes / OpenShift (non-root) | כן — אחד מהשניים |
156156
| `quay.io/martinhelmich/prometheus-nginxlog-exporter` | `v1.11.0` | 4040 | מטריקות Prometheus (כולל זמני תגובה) | לא — רק אם `metrics.enabled=true` |
157-
| `a0533057932/redis-docs-cli` | `latest` / `0.3.1` | 8090 | CLI playground proxy (Flask) | לא — רק אם `cli.enabled=true` |
157+
| `a0533057932/redis-docs-cli` | `latest` / `0.3.3` | 8090 | CLI playground proxy (Flask) | לא — רק אם `cli.enabled=true` |
158158
| `redis` | `8-alpine` | 6379 | Redis sidecar ל-CLI playground | לא — רק אם `cli.enabled=true` |
159159
| `quay.io/jupyter/minimal-notebook` | `2026-04-02` | 8888 | Jupyter kernel server להרצת קוד אינטראקטיבי | לא — רק אם `cli.jupyter.enabled=true` |
160160

@@ -416,8 +416,8 @@ docker push REGISTRY/prometheus-nginxlog-exporter:v1.11.0
416416
417417
# טעינת CLI (אופציונלי)
418418
docker load -i redis-docs-cli.tar
419-
docker tag a0533057932/redis-docs-cli:latest REGISTRY/redis-docs-cli:0.3.1
420-
docker push REGISTRY/redis-docs-cli:0.3.1
419+
docker tag a0533057932/redis-docs-cli:latest REGISTRY/redis-docs-cli:0.3.3
420+
docker push REGISTRY/redis-docs-cli:0.3.3
421421
422422
docker load -i redis.tar
423423
docker tag redis:8-alpine REGISTRY/redis:8-alpine
@@ -548,7 +548,7 @@ kubectl port-forward svc/redis-docs 8080:80
548548
| `cli.securityContext.runAsNonRoot` | `true` | חסימת הרצה כ-root (CLI) |
549549
| `cli.image.registry` | `a0533057932` | registry לתמונת CLI proxy |
550550
| `cli.image.name` | `redis-docs-cli` | שם תמונת CLI proxy |
551-
| `cli.image.tag` | `latest` | תג תמונת CLI proxy (ברשת סגורה: `0.3.1`) |
551+
| `cli.image.tag` | `latest` | תג תמונת CLI proxy (ברשת סגורה: `0.3.3`) |
552552
| `cli.image.pullPolicy` | `IfNotPresent` | מדיניות משיכת תמונת CLI |
553553
| `cli.resources` | requests: 50m/64Mi, limits: 200m/128Mi | משאבי CLI proxy |
554554
| `cli.redis.image.registry` | `docker.io` | registry לתמונת Redis |

helm/redis-docs/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ so external URLs an author wrote by hand in markdown remain untouched.
194194
| `a0533057932/redis-docs` | `<HASH>` / `latest` | 80 | Standard run with `docker run` (privileged) | Yes — one of the two |
195195
| `a0533057932/redis-docs` | `<HASH>-unprivileged` / `unprivileged` | 8080 | Kubernetes / OpenShift (non-root) | Yes — one of the two |
196196
| `quay.io/martinhelmich/prometheus-nginxlog-exporter` | `v1.11.0` | 4040 | Prometheus metrics (including response times) | No — only if `metrics.enabled=true` |
197-
| `a0533057932/redis-docs-cli` | `latest` / `0.3.1` | 8090 | CLI playground proxy (Flask) | No — only if `cli.enabled=true` |
197+
| `a0533057932/redis-docs-cli` | `latest` / `0.3.3` | 8090 | CLI playground proxy (Flask) | No — only if `cli.enabled=true` |
198198
| `redis` | `8-alpine` | 6379 | Redis sidecar for CLI playground | No — only if `cli.enabled=true` |
199199
| `quay.io/jupyter/minimal-notebook` | `2026-04-02` | 8888 | Jupyter kernel server for interactive code execution | No — only if `cli.jupyter.enabled=true` |
200200

@@ -453,8 +453,8 @@ docker push REGISTRY/prometheus-nginxlog-exporter:v1.11.0
453453
454454
# Load CLI (optional)
455455
docker load -i redis-docs-cli.tar
456-
docker tag a0533057932/redis-docs-cli:latest REGISTRY/redis-docs-cli:0.3.1
457-
docker push REGISTRY/redis-docs-cli:0.3.1
456+
docker tag a0533057932/redis-docs-cli:latest REGISTRY/redis-docs-cli:0.3.3
457+
docker push REGISTRY/redis-docs-cli:0.3.3
458458
459459
docker load -i redis.tar
460460
docker tag redis:8-alpine REGISTRY/redis:8-alpine
@@ -585,7 +585,7 @@ A ready-to-import dashboard file is located at `helm/dashboards/redis-docs-nginx
585585
| `cli.securityContext.runAsNonRoot` | `true` | Block running as root (CLI) |
586586
| `cli.image.registry` | `a0533057932` | CLI proxy image registry |
587587
| `cli.image.name` | `redis-docs-cli` | CLI proxy image name |
588-
| `cli.image.tag` | `latest` | CLI proxy image tag (in air-gapped networks: `0.3.1`) |
588+
| `cli.image.tag` | `latest` | CLI proxy image tag (in air-gapped networks: `0.3.3`) |
589589
| `cli.image.pullPolicy` | `IfNotPresent` | CLI image pull policy |
590590
| `cli.resources` | requests: 50m/64Mi, limits: 200m/128Mi | CLI proxy resources |
591591
| `cli.redis.image.registry` | `docker.io` | Redis image registry |

helm/redis-docs/templates/deployment-cli.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ spec:
4141
port: http
4242
initialDelaySeconds: 5
4343
periodSeconds: 10
44+
timeoutSeconds: 3
4445
readinessProbe:
4546
httpGet:
4647
path: /healthz
4748
port: http
4849
initialDelaySeconds: 3
4950
periodSeconds: 5
51+
timeoutSeconds: 3
5052
resources:
5153
{{- toYaml .Values.cli.resources | nindent 12 }}
5254
securityContext:

0 commit comments

Comments
 (0)