]> git.eshelyaron.com Git - kubed.git/commitdiff
Fix handling of context names with non-alphanumeric characters
authorEshel Yaron <me@eshelyaron.com>
Tue, 10 Jun 2025 17:04:11 +0000 (19:04 +0200)
committerEshel Yaron <me@eshelyaron.com>
Tue, 10 Jun 2025 17:42:28 +0000 (19:42 +0200)
Hex-encode most non-alphanumeric characters in kubectl context names
in Tramp file names.  This allows our Tramp method to work with
context names that contain characters which are not allowed in the
host name part of Tramp file names, such as ':'.

This (hopefully) fixes the issue reported at
https://lists.sr.ht/~eshel/kubed-devel/%3C87ldqz70a6.fsf@gmail.com%3E

* kubed-common.el (kubed-tramp-method): Bump to v2.
* kubed-tramp.el (kubed-tramp--unhex)
(kubed-tramp--decode-context-name)
(kubed-tramp--v2-context): New functions.
(kubed-tramp-context, kubed-tramp-enable): Adjust.
* kubed.el (kubed--hex-encoding-table)
(kubed--hex-allowed-chars-table): New constants.
(kubed--encode-context-name): New function.
(kubed-remote-file-name): Use it.

kubed-common.el
kubed-tramp.el
kubed.el

index 15e43090caec1d06dbbb81802a09fa8d2e8cee84..f7cb240ed58664fb7bbfe34eba951932f9ee513d 100644 (file)
@@ -12,8 +12,8 @@
 
 ;;; Code:
 
-(defvar kubed-tramp-method "kubedv1"    ;Versioned, for compatibility.
-  ;; (find-file "/kubedv1:CONTEXT%NAMESPACE%POD%CONTAINER:/some/file")
+(defvar kubed-tramp-method "kubedv2"    ;Versioned, for compatibility.
+  ;; (find-file "/kubedv2:CONTEXT%NAMESPACE%POD%CONTAINER:/some/file")
   "Name of the Kubed Tramp method.")
 
 (defcustom kubed-kubectl-program "kubectl"
index f355c49e3108b30c8e475b217dac5942ddc3ee25..9e6fbac4e4865051154884a69aefaab91ce9c0f3 100644 (file)
 (require 'kubed-common)
 (require 'tramp)
 
+(defun kubed-tramp--unhex (x)
+  ;; Simplified version of `url-unhex'.
+  (if (> x ?9) (+ 10 (- x ?A)) (- x ?0)))
+
+(defun kubed-tramp--decode-context-name (str)
+  ;; Adopted from `url-unhex-string'.
+  (let ((tmp "") (case-fold-search nil))
+    (while (string-match "[.][0-9A-F][0-9A-F]" str)
+      (let* ((start (match-beginning 0))
+            (ch1 (kubed-tramp--unhex (elt str (+ start 1))))
+            (code (+ (* 16 ch1)
+                     (kubed-tramp--unhex (elt str (+ start 2))))))
+       (setq tmp (concat tmp (substring str 0 start) (byte-to-string code))
+             str (substring str (match-end 0)))))
+    (concat tmp str)))
+
+(defun kubed-tramp--v2-context (vec)
+  "Extract the context name from a kubernetes host name in VEC."
+  (or (when-let ((host (and vec (tramp-file-name-host vec))))
+        (shell-quote-argument
+         (decode-coding-string
+         (kubed-tramp--decode-context-name (nth 0 (split-string host "%")))
+         'utf-8)))
+      ""))
+
 (defun kubed-tramp--context (vec)
   "Extract the context name from a kubernetes host name in VEC."
   (or (when-let ((host (and vec (tramp-file-name-host vec))))
 ;;;###autoload
 (defun kubed-tramp-context (file-name)
   "Extract `kubectl' context from Kubed Tramp remote file name FILE-NAME."
-  (nth 0 (split-string
-          (tramp-file-name-host (tramp-dissect-file-name file-name)) "%")))
+  ;; TODO: Dispatch based on method version.  The following is intended
+  ;; for v2, although it also works for v1 in most cases.
+  (decode-coding-string
+   (kubed-tramp--decode-context-name
+    (nth 0 (split-string
+            (tramp-file-name-host (tramp-dissect-file-name file-name))
+            "%")))
+   'utf-8))
 
 ;;;###autoload
 (defun kubed-tramp-namespace (file-name)
   "Enable Kubed integration with Tramp."
   (when (boundp 'tramp-extra-expand-args) ; Tramp 2.7+
 
-    (setf (alist-get kubed-tramp-method tramp-methods nil nil #'string=)
-          `((tramp-login-program ,kubed-kubectl-program)
-            (tramp-login-args (("exec")
-                               ("--context" "%x")
-                               ("--namespace" "%y")
-                               ("-c" "%a")
-                               ("%h")
-                               ("-it")
-                               ("--")
-                              ("%l")))
-            (tramp-direct-async (,tramp-default-remote-shell "-c"))
-            (tramp-remote-shell ,tramp-default-remote-shell)
-            (tramp-remote-shell-login ("-l"))
-            (tramp-remote-shell-args ("-i" "-c"))))
-
-    (connection-local-set-profile-variables
-     'kubed-tramp-connection-local-default-profile
-     '((tramp-extra-expand-args
-        ?a (kubed-tramp--container (car tramp-current-connection))
-        ?h (kubed-tramp--pod       (car tramp-current-connection))
-        ?x (kubed-tramp--context   (car tramp-current-connection))
-        ?y (kubed-tramp--namespace (car tramp-current-connection)))))
-
-    (connection-local-set-profiles
-     `(:application tramp :protocol ,kubed-tramp-method)
-     'kubed-tramp-connection-local-default-profile)))
+    (let ((params `((tramp-login-program ,kubed-kubectl-program)
+                    (tramp-login-args (("exec")
+                                       ("--context" "%x")
+                                       ("--namespace" "%y")
+                                       ("-c" "%a")
+                                       ("%h")
+                                       ("-it")
+                                       ("--")
+                                      ("%l")))
+                    (tramp-direct-async (,tramp-default-remote-shell "-c"))
+                    (tramp-remote-shell ,tramp-default-remote-shell)
+                    (tramp-remote-shell-login ("-l"))
+                    (tramp-remote-shell-args ("-i" "-c")))))
+
+      ;; Old version.
+      (setf (alist-get "kubedv1" tramp-methods nil nil #'string=) params)
+
+      (connection-local-set-profile-variables
+       'kubed-tramp-connection-local-default-profile
+       '((tramp-extra-expand-args
+          ?a (kubed-tramp--container  (car tramp-current-connection))
+          ?h (kubed-tramp--pod        (car tramp-current-connection))
+          ?x (kubed-tramp--context    (car tramp-current-connection))
+          ?y (kubed-tramp--namespace  (car tramp-current-connection)))))
+
+      (connection-local-set-profiles
+       '(:application tramp :protocol "kubedv1")
+       'kubed-tramp-connection-local-default-profile)
+
+      ;; New version.
+      (setf (alist-get kubed-tramp-method tramp-methods nil nil #'string=) params)
+
+      (connection-local-set-profile-variables
+       'kubed-tramp-v2-connection-local-default-profile
+       '((tramp-extra-expand-args
+          ?a (kubed-tramp--container  (car tramp-current-connection))
+          ?h (kubed-tramp--pod        (car tramp-current-connection))
+          ?x (kubed-tramp--v2-context (car tramp-current-connection))
+          ?y (kubed-tramp--namespace  (car tramp-current-connection)))))
+
+      (connection-local-set-profiles
+       `(:application tramp :protocol ,kubed-tramp-method)
+       'kubed-tramp-v2-connection-local-default-profile))))
 
 ;;;###autoload (with-eval-after-load 'tramp (kubed-tramp-enable))
 
index 9a9ec6a91ca275d3aa9214505ef6724cf1144067..2a32d36850fb09ce235ba2796bca9b62eeea6530 100644 (file)
--- a/kubed.el
+++ b/kubed.el
@@ -1673,10 +1673,33 @@ Interactively, use the current context.  With a prefix argument
                "\\)")
        1))
 
+(defconst kubed--hex-encoding-table
+  (let ((vec (make-vector 256 nil)))
+    (dotimes (byte 256) (aset vec byte (format ".%02X" byte))) vec))
+
+(defconst kubed--hex-allowed-chars-table
+  (let ((vec (make-vector 256 nil)))
+    (dolist (byte '( ?a ?b ?c ?d ?e ?f ?g ?h ?i ?j ?k ?l ?m ?n ?o ?p ?q ?r ?s ?t ?u ?v ?w ?x ?y ?z
+                     ?A ?B ?C ?D ?E ?F ?G ?H ?I ?J ?K ?L ?M ?N ?O ?P ?Q ?R ?S ?T ?U ?V ?W ?X ?Y ?Z
+                     ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9
+                     ?- ?_))
+      (ignore-errors (aset vec byte t)))
+    vec))
+
+(defun kubed--encode-context-name (str)
+  ;; Adopted from `url-hexify-string'.
+  (mapconcat (lambda (byte)
+              (if (aref kubed--hex-allowed-chars-table byte)
+                  (char-to-string byte)
+                (aref kubed--hex-encoding-table byte)))
+            (if (multibyte-string-p str)
+                (encode-coding-string str 'utf-8)
+              str)))
+
 (defun kubed-remote-file-name (context namespace pod &optional file-name)
   "Return remote FILE-NAME for POD in NAMESPACE and CONTEXT."
   (concat "/" kubed-tramp-method ":"
-          context "%" namespace "%" pod
+          (kubed--encode-context-name context) "%" namespace "%" pod
           "%" (kubed-read-container pod "Container" t context namespace)
           ":" file-name))