fix: keep ssh route operation boundaries
This commit is contained in:
+2
-1
@@ -166,7 +166,8 @@ export function sshHelp(): unknown {
|
||||
notes: [
|
||||
"ssh --help and ssh <route> --help print this JSON help and never open an interactive session.",
|
||||
"For non-interactive remote commands, prefer argv for a single process and script/stdin for shell logic.",
|
||||
"Route syntax is provider:plane[:namespace:resource[:container]] and locates a distributed target only. For native k3s providers such as D601 and G14, <provider>:k3s locates the control plane, <provider>:k3s:<namespace>:<workload> locates a workload, and kubectl/script/logs/apply-patch/exec are operations placed after the route.",
|
||||
"Route syntax is `{provider}:{plane}[:{scope...}] {operation} [operation-args...]`: the first argv token locates a distributed target only, and every following token belongs to the operation parser. For native k3s providers such as D601 and G14, <provider>:k3s locates the control plane, <provider>:k3s:<namespace>:<workload> locates a workload, and kubectl/script/logs/apply-patch/exec are operations placed after the route.",
|
||||
"Do not put operation names in any colon route segment, including nested k3s namespace/workload/container segments.",
|
||||
"Do not use post-provider shorthand such as `ssh G14 k3s ...`; write `ssh G14:k3s ...` so location and operation stay separated.",
|
||||
"If an ssh-like remote command fails with timeout/kex/exit-255 friction, stderr includes one low-noise UNIDESK_SSH_HINT JSON line with the argv retry command.",
|
||||
"Use -- before a remote command that intentionally starts with a dash.",
|
||||
|
||||
+5
-7
@@ -867,7 +867,8 @@ export function parseSshRoute(target: string): ParsedSshRoute {
|
||||
}
|
||||
if (plane !== "k3s") throw new Error(`unsupported ssh route plane: ${plane}`);
|
||||
const [first, second, third, fourth] = rest;
|
||||
if (first && legacyK3sOperationRouteSegments.has(first)) throw new Error(k3sOperationInRouteMessage(target, first, second, third));
|
||||
const operationInRoute = [first, second, third].find((segment) => segment !== undefined && legacyK3sOperationRouteSegments.has(segment));
|
||||
if (operationInRoute !== undefined) throw new Error(k3sOperationInRouteMessage(target, operationInRoute));
|
||||
if (fourth !== undefined) throw new Error("ssh k3s target route supports at most provider:k3s:namespace:resource:container");
|
||||
return {
|
||||
providerId,
|
||||
@@ -880,13 +881,10 @@ export function parseSshRoute(target: string): ParsedSshRoute {
|
||||
};
|
||||
}
|
||||
|
||||
function k3sOperationInRouteMessage(target: string, operation: string, namespace: string | undefined, resource: string | undefined): string {
|
||||
function k3sOperationInRouteMessage(target: string, operation: string): string {
|
||||
const providerId = target.split(":")[0] || "<provider>";
|
||||
if (operation === "kubectl") return `ssh k3s route must locate a target only; use "ssh ${providerId}:k3s kubectl ..." instead of "${target}"`;
|
||||
if (operation === "script" && namespace === undefined) return `ssh k3s route must locate a target only; use "ssh ${providerId}:k3s script <<'SCRIPT'" instead of "${target}"`;
|
||||
if (operation === "guard") return `ssh k3s route must locate a target only; use "ssh ${providerId}:k3s guard" or "ssh ${providerId}:k3s" instead of "${target}"`;
|
||||
if (namespace !== undefined && resource !== undefined) return `ssh k3s route must locate a target only; use "ssh ${providerId}:k3s:${namespace}:${resource} ${operation} ..." instead of "${target}"`;
|
||||
return `ssh k3s route must locate a target only; put operation "${operation}" after the route, for example "ssh ${providerId}:k3s ${operation} ..."`;
|
||||
const operationExample = operation === "guard" ? "guard" : `${operation} ...`;
|
||||
return `ssh k3s route must locate a target only; put operation "${operation}" after the route, for example "ssh ${providerId}:k3s ${operationExample}" or "ssh ${providerId}:k3s:<namespace>:<workload> ${operationExample}" instead of "${target}"`;
|
||||
}
|
||||
|
||||
function shellArgv(args: string[]): string {
|
||||
|
||||
@@ -82,6 +82,16 @@ export function runSshArgvGuidanceContract(): JsonRecord {
|
||||
/route must locate a target only.*ssh D601:k3s kubectl/u,
|
||||
"operation names must not be accepted as k3s route segments",
|
||||
);
|
||||
assertThrows(
|
||||
() => parseSshInvocation("D601:k3s:hwlab-ci:kubectl", ["get", "pods"]),
|
||||
/route must locate a target only.*operation "kubectl" after the route/u,
|
||||
"operation names must not be accepted as nested k3s route segments",
|
||||
);
|
||||
assertThrows(
|
||||
() => parseSshInvocation("D601:k3s:hwlab-dev:hwlab-cloud-api:logs", []),
|
||||
/route must locate a target only.*operation "logs" after the route/u,
|
||||
"operation names must not be accepted as k3s container route segments",
|
||||
);
|
||||
assertThrows(
|
||||
() => parseSshInvocation("D601:k3s:apply-patch:hwlab-dev:hwlab-cloud-api", []),
|
||||
/route must locate a target only.*apply-patch/u,
|
||||
@@ -121,7 +131,7 @@ export function runSshArgvGuidanceContract(): JsonRecord {
|
||||
"argv form is classified and quoted as the success path for non-interactive commands",
|
||||
"stdin script form removes shell-command strings for host and k3s workload scripts",
|
||||
"pod apply-patch operation injects helper and forwards patch stdin",
|
||||
"legacy operation-in-route forms are rejected with canonical route-plus-operation guidance",
|
||||
"legacy operation-in-route forms are rejected in any k3s route segment with canonical route-plus-operation guidance",
|
||||
"post-provider k3s shorthand is rejected so location and operation stay separated",
|
||||
"k3s route stays location-only while operations fix native kubeconfig and assemble kubectl exec as argv",
|
||||
"ssh-like timeout/kex failures emit one structured argv retry hint",
|
||||
|
||||
Reference in New Issue
Block a user