diff options
Diffstat (limited to 'lisp/net/tramp-container.el')
-rw-r--r-- | lisp/net/tramp-container.el | 433 |
1 files changed, 384 insertions, 49 deletions
diff --git a/lisp/net/tramp-container.el b/lisp/net/tramp-container.el index 6e63a8b55ec..30639cbeb85 100644 --- a/lisp/net/tramp-container.el +++ b/lisp/net/tramp-container.el @@ -31,29 +31,58 @@ ;; Open a file on a running Docker container: ;; ;; C-x C-f /docker:USER@CONTAINER:/path/to/file +;; C-x C-f /dockercp:USER@CONTAINER:/path/to/file ;; ;; or Podman: ;; ;; C-x C-f /podman:USER@CONTAINER:/path/to/file +;; C-x C-f /podmancp: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. +;; +;; "docker" and "podman" are inline methods, "dockercp" and "podmancp" +;; are out-of-band methods. +;; ;; ;; ;; 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: ;; ;; "kubectl config set-context --current --namespace=<name>" +;; +;; +;; +;; Open a file on an existing Toolbox container: +;; +;; C-x C-f /toolbox:CONTAINER:/path/to/file +;; +;; Where: +;; 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. +;; +;; +;; +;; Open a file on a running Flatpak sandbox: +;; +;; C-x C-f /flatpak:SANDBOX:/path/to/file +;; +;; Where: +;; SANDBOX is the running sandbox to connect to. +;; It could be an application ID, an instance ID, or a PID. ;;; Code: @@ -83,73 +112,268 @@ :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." + :group 'tramp + :version "30.1" + :type '(choice (const "toolbox") + (string))) + +;;;###tramp-autoload +(defcustom tramp-flatpak-program "flatpak" + "Name of the Flatpak client program." + :group 'tramp + :version "30.1" + :type '(choice (const "flatpak") + (string))) + ;;;###tramp-autoload (defconst tramp-docker-method "docker" "Tramp method name to use to connect to Docker containers.") ;;;###tramp-autoload +(defconst tramp-dockercp-method "dockercp" + "Tramp method name to use to connect to Docker containers. +This is for out-of-band connections.") + +;;;###tramp-autoload (defconst tramp-podman-method "podman" "Tramp method name to use to connect to Podman containers.") ;;;###tramp-autoload +(defconst tramp-podmancp-method "podmancp" + "Tramp method name to use to connect to Podman containers. +This is for out-of-band connections.") + +;;;###tramp-autoload (defconst tramp-kubernetes-method "kubernetes" "Tramp method name to use to connect to Kubernetes containers.") ;;;###tramp-autoload -(defun tramp-container--completion-function (program) +(defconst tramp-toolbox-method "toolbox" + "Tramp method name to use to connect to Toolbox containers.") + +;;;###tramp-autoload +(defconst tramp-flatpak-method "flatpak" + "Tramp method name to use to connect to Flatpak sandboxes.") + +;;;###tramp-autoload +(defmacro tramp-skeleton-completion-function (method &rest body) + "Skeleton for `tramp-*-completion-function' with multi-hop support. +BODY is the backend specific code." + (declare (indent 1) (debug t)) + `(let* ((default-directory + (or (and (member ,method tramp-completion-multi-hop-methods) + tramp--last-hop-directory) + tramp-compat-temporary-file-directory)) + (program (let ((tramp-verbose 0)) + (tramp-get-method-parameter + (make-tramp-file-name :method ,method) + 'tramp-login-program))) + (vec (when (tramp-tramp-file-p default-directory) + (tramp-dissect-file-name default-directory))) + non-essential) + ;; We don't use connection properties, because this information + ;; shouldn't be kept persistently. + (with-tramp-file-property + vec (concat "/" ,method ":") "user-host-completions" + ,@body))) + +;;;###tramp-autoload +(defun tramp-container--completion-function (method) "List running containers available for connection. -PROGRAM is the program to be run for \"ps\", either -`tramp-docker-program' or `tramp-podman-program'. +METHOD is the Tramp method to be used for \"ps\", either +`tramp-docker-method', `tramp-dockercp-method', `tramp-podman-method', +or `tramp-podmancp-method'. 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 program " ps --format '{{.ID}}\t{{.Names}}'"))) - (lines (split-string raw-list "\n" 'omit)) - (names (mapcar - (lambda (line) - (when (string-match - (rx bol (group (1+ nonl)) - "\t" (? (group (1+ nonl))) eol) - line) - (or (match-string 2 line) (match-string 1 line)))) - lines))) - (mapcar (lambda (name) (list nil name)) (delq nil names)))) - -;;;###tramp-autoload -(defun tramp-kubernetes--completion-function (&rest _args) + (tramp-skeleton-completion-function method + (when-let ((raw-list + (shell-command-to-string + (concat program " ps --format '{{.ID}}\t{{.Names}}'"))) + (lines (split-string raw-list "\n" 'omit)) + (names + (mapcar + (lambda (line) + (when (string-match + (rx bol (group (1+ nonl)) + "\t" (? (group (1+ nonl))) eol) + line) + (or (match-string 2 line) (match-string 1 line)))) + lines))) + (mapcar (lambda (name) (list nil name)) (delq nil names))))) + +;;;###tramp-autoload +(defun tramp-kubernetes--completion-function (method) "List Kubernetes pods available for connection. 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)) names))) + (tramp-skeleton-completion-function method + (when-let ((raw-list + (shell-command-to-string + (concat + program " " + (tramp-kubernetes--context-namespace vec) + " 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))) + "")) + +;; We must change `vec' and `default-directory' to the previous hop, +;; in order to run `process-file' in a proper environment. +(defmacro tramp-skeleton-kubernetes-vector (vec &rest body) + "Skeleton for `tramp-kubernetes--current-context*' with multi-hop support. +BODY is the backend specific code." + (declare (indent 1) (debug t)) + `(let* ((vec + (cond + ((null ,vec) tramp-null-hop) + ((equal (tramp-file-name-method ,vec) tramp-kubernetes-method) + (if (tramp-file-name-hop ,vec) + (tramp-dissect-hop-name (tramp-file-name-hop ,vec)) + tramp-null-hop)) + (t ,vec))) + (default-directory + (if (equal vec tramp-null-hop) + tramp-compat-temporary-file-directory + (tramp-make-tramp-file-name vec "/")))) + ,@body)) + +(defun tramp-kubernetes--current-context (vec) + "Return Kubernetes current context. +Obey `tramp-kubernetes-context'" + (or tramp-kubernetes-context + (tramp-skeleton-kubernetes-vector vec + (with-tramp-file-property + vec (concat "/" tramp-kubernetes-method ":") "current-context" + (with-temp-buffer + (when (zerop + (process-file + 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-let ((current-context (tramp-kubernetes--current-context vec))) + (tramp-skeleton-kubernetes-vector vec + (with-temp-buffer (when (zerop - (tramp-call-process - vec tramp-kubernetes-program nil t nil + (process-file + 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 as string." + (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 (method) + "List Toolbox containers available for connection. + +This function is used by `tramp-set-completion-function', please +see its function help for a description of the format." + (tramp-skeleton-completion-function method + (when-let ((raw-list (shell-command-to-string (concat program " list -c"))) + ;; Ignore header line. + (lines (cdr (split-string raw-list "\n" 'omit))) + (names (mapcar + (lambda (line) + (when (string-match + (rx bol (1+ (not space)) + (1+ space) (group (1+ (not space))) space) + line) + (match-string 1 line))) + lines))) + (mapcar (lambda (name) (list nil name)) (delq nil names))))) + +;;;###tramp-autoload +(defun tramp-flatpak--completion-function (method) + "List Flatpak sandboxes available for connection. +It returns application IDs or, in case there is no application +ID, instance IDs. + +This function is used by `tramp-set-completion-function', please +see its function help for a description of the format." + (tramp-skeleton-completion-function method + (when-let ((raw-list + (shell-command-to-string + ;; Ignore header line. + (concat program " ps --columns=instance,application | cat -"))) + (lines (split-string raw-list "\n" 'omit)) + (names (mapcar + (lambda (line) + (when (string-match + (rx bol (* space) (group (+ (not space))) + (? (+ space) (group (+ (not space)))) eol) + line) + (or (match-string 2 line) (match-string 1 line)))) + lines))) + (mapcar (lambda (name) (list nil name)) (delq nil names))))) + +;;;###tramp-autoload (defvar tramp-default-remote-shell) ;; Silence byte compiler. ;;;###tramp-autoload @@ -168,6 +392,23 @@ see its function help for a description of the format." (tramp-remote-shell-args ("-i" "-c")))) (add-to-list 'tramp-methods + `(,tramp-dockercp-method + (tramp-login-program ,tramp-docker-program) + (tramp-login-args (("exec") + ("-it") + ("-u" "%u") + ("%h") + ("%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")) + (tramp-copy-program ,tramp-docker-program) + (tramp-copy-args (("cp"))) + (tramp-copy-file-name (("%h" ":") ("%f"))) + (tramp-copy-recursive t))) + + (add-to-list 'tramp-methods `(,tramp-podman-method (tramp-login-program ,tramp-podman-program) (tramp-login-args (("exec") @@ -181,32 +422,126 @@ see its function help for a description of the format." (tramp-remote-shell-args ("-i" "-c")))) (add-to-list 'tramp-methods + `(,tramp-podmancp-method + (tramp-login-program ,tramp-podman-program) + (tramp-login-args (("exec") + ("-it") + ("-u" "%u") + ("%h") + ("%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")) + (tramp-copy-program ,tramp-podman-program) + (tramp-copy-args (("cp"))) + (tramp-copy-file-name (("%h" ":") ("%f"))) + (tramp-copy-recursive t))) + + (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")) (tramp-remote-shell-args ("-i" "-c")))) + (add-to-list 'tramp-methods + `(,tramp-toolbox-method + (tramp-login-program ,tramp-toolbox-program) + (tramp-login-args (("run") + ("-c" "%h") + ("%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 ("-c")))) + + (add-to-list 'tramp-default-host-alist `(,tramp-toolbox-method nil "")) + + (add-to-list 'tramp-methods + `(,tramp-flatpak-method + (tramp-login-program ,tramp-flatpak-program) + (tramp-login-args (("enter") + ("%h") + ("%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 ("-c")))) + (tramp-set-completion-function tramp-docker-method - `((tramp-container--completion-function - ,(executable-find tramp-docker-program)))) + `((tramp-container--completion-function ,tramp-docker-method))) + + (tramp-set-completion-function + tramp-dockercp-method + `((tramp-container--completion-function ,tramp-dockercp-method))) (tramp-set-completion-function tramp-podman-method - `((tramp-container--completion-function - ,(executable-find tramp-podman-program)))) + `((tramp-container--completion-function ,tramp-podman-method))) + + (tramp-set-completion-function + tramp-podmancp-method + `((tramp-container--completion-function ,tramp-podmancp-method))) (tramp-set-completion-function tramp-kubernetes-method - '((tramp-kubernetes--completion-function "")))) + `((tramp-kubernetes--completion-function ,tramp-kubernetes-method))) + + (tramp-set-completion-function + tramp-toolbox-method + `((tramp-toolbox--completion-function ,tramp-toolbox-method))) + + (tramp-set-completion-function + tramp-flatpak-method + `((tramp-flatpak--completion-function ,tramp-flatpak-method))) + + (add-to-list 'tramp-completion-multi-hop-methods tramp-docker-method) + (add-to-list 'tramp-completion-multi-hop-methods tramp-podman-method) + (add-to-list 'tramp-completion-multi-hop-methods tramp-kubernetes-method) + (add-to-list 'tramp-completion-multi-hop-methods tramp-toolbox-method) + (add-to-list 'tramp-completion-multi-hop-methods tramp-flatpak-method) + + ;; Default connection-local variables for Tramp. + + (defconst tramp-kubernetes-connection-local-default-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-kubernetes-connection-local-default-profile + tramp-kubernetes-connection-local-default-variables) + + (connection-local-set-profiles + `(:application tramp :protocol ,tramp-kubernetes-method) + 'tramp-kubernetes-connection-local-default-profile) + + (defconst tramp-flatpak-connection-local-default-variables + `((tramp-remote-path . ,(cons "/app/bin" tramp-remote-path))) + "Default connection-local variables for remote flatpak connections.") + + (connection-local-set-profile-variables + 'tramp-flatpak-connection-local-default-profile + tramp-flatpak-connection-local-default-variables) + + (connection-local-set-profiles + `(:application tramp :protocol ,tramp-flatpak-method) + 'tramp-flatpak-connection-local-default-profile)) (add-hook 'tramp-unload-hook (lambda () |