From 77c2f05d773271cb59ebfd994b06a4075cacbfa8 Mon Sep 17 00:00:00 2001 From: Michael Albinus Date: Fri, 23 Jun 2023 21:26:14 +0200 Subject: [PATCH] Extend Tramp kubernetes method * doc/misc/tramp.texi (Inline methods): Adapt kubernetes method. * etc/NEWS: Describe changes in Tramp kubernetes method. * lisp/net/tramp-container.el (tramp-kubernetes-context) (tramp-kubernetes-namespace): New defcustoms. (tramp-kubernetes--completion-function): Extend for CONTAINER.POD syntax. (tramp-kubernetes--host-name-regexp): New defconst. (tramp-kubernetes--container, tramp-kubernetes--pod) (tramp-kubernetes--current-context): New defuns. (tramp-kubernetes--current-context-data): Simplify. (tramp-kubernetes--context-namespace): New defun. (tramp-methods) : Respect container, context and namespace. (Bug#59797) (tramp-container-connection-local-default-kubernetes-variables): New defconst. Set respective connection-local variables. * lisp/net/tramp-sh.el (tramp-config-check): New variable. (tramp-open-connection-setup-interactive-shell): Use it. * lisp/net/tramp.el (tramp-methods): Adapt docstring. (tramp-extra-expand-args): New defvar. (tramp-expand-args): Use it. --- doc/misc/tramp.texi | 11 ++- etc/NEWS | 8 ++ lisp/net/tramp-container.el | 145 ++++++++++++++++++++++++++++-------- lisp/net/tramp-sh.el | 10 ++- lisp/net/tramp.el | 29 +++++--- 5 files changed, 161 insertions(+), 42 deletions(-) diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi index eb5c418728e..01f46865a39 100644 --- a/doc/misc/tramp.texi +++ b/doc/misc/tramp.texi @@ -922,8 +922,15 @@ if desired. @cindex @option{kubernetes} method Integration for containers in Kubernetes pods. The host name is a pod -name returned by @samp{kubectl get pods}. The first container in a -pod is used. +name returned by @samp{kubectl get pods}, or +@samp{@var{container}.@var{pod}} if an explicit container name shall +be used. Otherwise, the first container in a pod is used. + +@vindex tramp-kubernetes-context +@vindex tramp-kubernetes-namespace +If another Kubernetes context or namespace shall be used, configure +the user options @code{tramp-kubernetes-context} and +@code{tramp-kubernetes-namespace}. This method does not support user names. diff --git a/etc/NEWS b/etc/NEWS index 88d432960f3..467ac3ee587 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -224,6 +224,14 @@ point is not in a comment or a string. It is by default bound to They allow accessing system containers provided by Toolbox or sandboxes provided by Flatpak. ++++ +*** Connection method "kubernetes" supports now optional container name. +The host name for Kubernetes connections can be of kind [CONTAINER.]POD, +in order to specify a dedicated container. If there is just the pod +name, the first container in the pod is taken. The new user options +'tramp-kubernetes-context' and 'tramp-kubernetes-namespace' allow to +access pods with different context or namespace but the default one. + +++ *** Rename 'tramp-use-ssh-controlmaster-options' to 'tramp-use-connection-share'. The old name still exists as obsolete variable alias. This user diff --git a/lisp/net/tramp-container.el b/lisp/net/tramp-container.el index 473cb1c54b8..6e8d28a3016 100644 --- a/lisp/net/tramp-container.el +++ b/lisp/net/tramp-container.el @@ -37,19 +37,20 @@ ;; C-x C-f /podman:USER@CONTAINER:/path/to/file ;; ;; Where: -;; USER is the user on the container to connect as (optional) -;; CONTAINER is the container to connect to +;; USER is the user on the container to connect as (optional). +;; CONTAINER is the container to connect to. ;; ;; ;; ;; Open file in a Kubernetes container: ;; -;; C-x C-f /kubernetes:POD:/path/to/file +;; C-x C-f /kubernetes:[CONTAINER.]POD:/path/to/file ;; ;; Where: -;; POD is the pod to connect to. -;; By default, the first container in that pod will be -;; used. +;; POD is the pod to connect to. +;; CONTAINER is the container to connect to (optional). +;; By default, the first container in that pod will +;; be used. ;; ;; Completion for POD and accessing it operate in the current ;; namespace, use this command to change it: @@ -63,7 +64,7 @@ ;; C-x C-f /toolbox:CONTAINER:/path/to/file ;; ;; Where: -;; CONTAINER is the container to connect to (optional) +;; CONTAINER is the container to connect to (optional). ;; ;; If the container is not running, it is started. If no container is ;; specified, the default Toolbox container is used. @@ -106,6 +107,20 @@ :type '(choice (const "kubectl") (string))) +(defcustom tramp-kubernetes-context nil + "Context of Kubernetes. +If it is nil, the default context will be used." + :group 'tramp + :version "30.1" + :type '(choice (const :tag "Use default" nil) + (string))) + +(defcustom tramp-kubernetes-namespace "default" + "Namespace of Kubernetes." + :group 'tramp + :version "30.1" + :type 'string) + ;;;###tramp-autoload (defcustom tramp-toolbox-program "toolbox" "Name of the Toolbox client program." @@ -172,29 +187,83 @@ This function is used by `tramp-set-completion-function', please see its function help for a description of the format." (when-let ((default-directory tramp-compat-temporary-file-directory) (raw-list (shell-command-to-string - (concat tramp-kubernetes-program - " get pods --no-headers " - "-o custom-columns=NAME:.metadata.name"))) - (names (split-string raw-list "\n" 'omit))) - (mapcar (lambda (name) (list nil name)) (delq nil names)))) + (concat + tramp-kubernetes-program " " + (tramp-kubernetes--context-namespace nil) + " get pods --no-headers" + ;; We separate pods by "|". Inside a pod, + ;; its name is separated from the containers + ;; by ":". Containers are separated by ",". + " -o jsonpath='{range .items[*]}{\"|\"}{.metadata.name}" + "{\":\"}{range .spec.containers[*]}{.name}{\",\"}" + "{end}{end}'"))) + (lines (split-string raw-list "|" 'omit))) + (let (names) + (dolist (line lines) + (setq line (split-string line ":" 'omit)) + ;; Pod name. + (push (car line) names) + ;; Container names. + (dolist (elt (split-string (cadr line) "," 'omit)) + (push (concat elt "." (car line)) names))) + (mapcar (lambda (name) (list nil name)) (delq nil names))))) + +(defconst tramp-kubernetes--host-name-regexp + (rx (? (group (regexp tramp-host-regexp)) ".") + (group (regexp tramp-host-regexp))) + "The CONTAINER.POD syntax of kubernetes host names in Tramp.") + +;;;###tramp-autoload +(defun tramp-kubernetes--container (vec) + "Extract the container name from a kubernetes host name in VEC." + (or (let ((host (tramp-file-name-host vec))) + (and (string-match tramp-kubernetes--host-name-regexp host) + (match-string 1 host))) + "")) + +;;;###tramp-autoload +(defun tramp-kubernetes--pod (vec) + "Extract the pod name from a kubernetes host name in VEC." + (or (let ((host (tramp-file-name-host vec))) + (and (string-match tramp-kubernetes--host-name-regexp host) + (match-string 2 host))) + "")) + +(defun tramp-kubernetes--current-context (vec) + "Return Kubernetes current context. +Obey `tramp-kubernetes-context'" + (or tramp-kubernetes-context + (with-tramp-connection-property nil "current-context" + (with-temp-buffer + (when (zerop + (tramp-call-process + vec tramp-kubernetes-program nil t nil + "config" "current-context")) + (goto-char (point-min)) + (buffer-substring (point) (line-end-position))))))) (defun tramp-kubernetes--current-context-data (vec) "Return Kubernetes current context data as JSON string." - (with-temp-buffer - (when (zerop - (tramp-call-process - vec tramp-kubernetes-program nil t nil - "config" "current-context")) - (goto-char (point-min)) - (let ((current-context (buffer-substring (point) (line-end-position)))) - (erase-buffer) - (when (zerop - (tramp-call-process - vec tramp-kubernetes-program nil t nil - "config" "view" "-o" - (format - "jsonpath='{.contexts[?(@.name == \"%s\")]}'" current-context))) - (buffer-string)))))) + (when-let ((current-context (tramp-kubernetes--current-context vec))) + (with-temp-buffer + (when (zerop + (tramp-call-process + vec tramp-kubernetes-program nil t nil + "config" "view" "-o" + (format + "jsonpath='{.contexts[?(@.name == \"%s\")]}'" current-context))) + (buffer-string))))) + +;;;###tramp-autoload +(defun tramp-kubernetes--context-namespace (vec) + "The kubectl options for context and namespace." + (mapconcat + #'identity + `(,(when-let ((context (tramp-kubernetes--current-context vec))) + (format "--context=%s" context)) + ,(when tramp-kubernetes-namespace + (format "--namespace=%s" tramp-kubernetes-namespace))) + " ")) ;;;###tramp-autoload (defun tramp-toolbox--completion-function (&rest _args) @@ -275,12 +344,13 @@ see its function help for a description of the format." (add-to-list 'tramp-methods `(,tramp-kubernetes-method (tramp-login-program ,tramp-kubernetes-program) - (tramp-login-args (("exec") + (tramp-login-args (("%x") ; context and namespace. + ("exec") + ("-c" "%a") ; container. ("%h") ("-it") ("--") ("%l"))) - (tramp-config-check tramp-kubernetes--current-context-data) (tramp-direct-async (,tramp-default-remote-shell "-c")) (tramp-remote-shell ,tramp-default-remote-shell) (tramp-remote-shell-login ("-l")) @@ -334,6 +404,23 @@ see its function help for a description of the format." ;; Default connection-local variables for Tramp. + (defconst tramp-container-connection-local-default-kubernetes-variables + '((tramp-config-check . tramp-kubernetes--current-context-data) + ;; This variable will be eval'ed in `tramp-expand-args'. + (tramp-extra-expand-args + . (?a (tramp-kubernetes--container (car tramp-current-connection)) + ?h (tramp-kubernetes--pod (car tramp-current-connection)) + ?x (tramp-kubernetes--context-namespace (car tramp-current-connection))))) + "Default connection-local variables for remote kubernetes connections.") + + (connection-local-set-profile-variables + 'tramp-container-connection-local-default-kubernetes-profile + tramp-container-connection-local-default-kubernetes-variables) + + (connection-local-set-profiles + `(:application tramp :protocol ,tramp-kubernetes-method) + 'tramp-container-connection-local-default-kubernetes-profile) + (defconst tramp-container-connection-local-default-flatpak-variables `((tramp-remote-path . ,(cons "/app/bin" tramp-remote-path))) "Default connection-local variables for remote flatpak connections.") diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el index da34f31fea6..d8231bd5bd2 100644 --- a/lisp/net/tramp-sh.el +++ b/lisp/net/tramp-sh.el @@ -4324,6 +4324,14 @@ seconds. If not, it produces an error message with the given ERROR-ARGS." (apply #'tramp-error-with-buffer (tramp-get-connection-buffer vec) vec 'file-error error-args))))) +(defvar tramp-config-check nil + "A function to be called with one argument, VEC. +It should return a string which is used to check, whether the +configuration of the remote host has been changed (which would +require to flush the cache data). This string is kept as +connection property \"config-check-data\". +This variable is intended as connection-local variable.") + (defun tramp-open-connection-setup-interactive-shell (proc vec) "Set up an interactive shell. Mainly sets the prompt and the echo correctly. PROC is the shell @@ -4370,7 +4378,7 @@ process to set up. VEC specifies the connection." vec "uname" (tramp-send-command-and-read vec "echo \\\"`uname -sr`\\\"")))) (config-check-function - (tramp-get-method-parameter vec 'tramp-config-check)) + (buffer-local-value 'tramp-config-check (process-buffer proc))) (old-config-check (and config-check-function (tramp-get-connection-property vec "config-check-data"))) diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el index e7928e4e1f4..cbd4e1611eb 100644 --- a/lisp/net/tramp.el +++ b/lisp/net/tramp.el @@ -300,13 +300,6 @@ pair of the form (KEY VALUE). The following KEYs are defined: and container methods do. If it is a list of strings, they are used to construct the remote command. - * `tramp-config-check' - A function to be called with one argument, VEC. It should - return a string which is used to check, whether the - configuration of the remote host has been changed (which - would require to flush the cache data). This string is kept - as connection property \"config-check-data\". - * `tramp-copy-program' This specifies the name of the program to use for remotely copying the file; this might be the absolute filename of scp or the name of @@ -4954,14 +4947,30 @@ Do not set it manually, it is used buffer-local in `tramp-get-lock-pid'.") ;; Result. target-alist)) +(defvar tramp-extra-expand-args nil + "Method specific arguments.") + (defun tramp-expand-args (vec parameter &rest spec-list) "Expand login arguments as given by PARAMETER in `tramp-methods'. PARAMETER is a symbol like `tramp-login-args', denoting a list of list of strings from `tramp-methods', containing %-sequences for -substitution. SPEC-LIST is a list of char/value pairs used for -`format-spec-make'." +substitution. +SPEC-LIST is a list of char/value pairs used for +`format-spec-make'. It is appended by `tramp-extra-expand-args', +a connection-local variable." (let ((args (tramp-get-method-parameter vec parameter)) - (spec (apply 'format-spec-make spec-list))) + (extra-spec-list + (mapcar + #'eval + (buffer-local-value + 'tramp-extra-expand-args (tramp-get-connection-buffer vec)))) + spec) + ;; Merge both spec lists. Remove duplicate entries. + (while spec-list + (unless (member (car spec-list) extra-spec-list) + (setq extra-spec-list (append (take 2 spec-list) extra-spec-list))) + (setq spec-list (cddr spec-list))) + (setq spec (apply #'format-spec-make extra-spec-list)) ;; Expand format spec. (flatten-tree (mapcar -- 2.39.5