bug-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

bug#62326: tramp-container PATH logic is incorrect


From: ParetoOptimalDev
Subject: bug#62326: tramp-container PATH logic is incorrect
Date: Mon, 20 Mar 2023 16:58:22 -0500
User-agent: Gnus/5.13 (Gnus v5.13)

For docker containers tramp should set the PATH equal to whatever `ENV
PATH=...` is in the Dockerfile. To be precise it should match the output of:

```
docker inspect --format='{{json .Config.Env}} $docker_container_id
```

Right now however, there is a piece of logic in tramp-get-remote-path
that removes non-existent directories:

(delq
 nil
 (mapcar
  (lambda (x)
    (and
     (stringp x)
     (file-directory-p (tramp-make-tramp-file-name vec x))
     x))
  remote-path))

This seems reasonable, but in practice it doesn't seem to work. Here is
a script to demonstrate with the haskell container from dockerhub:


(require 'tramp)

;; pre-require things that were making loading messages that clutter up
the batch output
(require 'em-alias)
(require 'em-banner)
(require 'em-basic)
(require 'em-cmpl)
(require 'em-extpipe)
(require 'em-glob)
(require 'em-hist)
(require 'em-ls)
(require 'em-pred)
(require 'em-prompt)
(require 'em-script)
(require 'em-term)
(require 'em-unix)

(setq edebug-print-length 500)
(setq tramp-verbose 7)

(setq existing-haskell-container-name "nifty_davinci")
(defun start-new-haskell-container ()
  """ returns container id"""
  (string-trim (shell-command-to-string "docker run -i --rm --detach 
haskell:slim@sha256:68280eb4fd3d4ee1b62cf40619fd6e956a741f693ef53e9dd4168d2c721880f5")))
(setq docker-container-id
      (or existing-haskell-container-name (start-new-haskell-container)))
(message (format "emacs-version: %s" emacs-version))
(message (format "tramp-version: %s" tramp-version))
(message "")
(message "using the docker haskell image")
(message "expected path is:")
(message (string-trim (shell-command-to-string (format "docker inspect 
--format='{{json .Config.Env}}' %s" docker-container-id))))

(let ((default-directory (concat "/docker:" docker-container-id ":"))
      (tramp-remote-path '(tramp-own-remote-path)))
  (tramp-cleanup-all-connections)
  (message (concat "path is: " (truncate-string-to-width (eshell-command-result 
"echo $PATH") 100))))


Here is what happens when I run the code above:

$ emacs -Q --batch --load config.el
emacs-version: 30.0.50
tramp-version: 2.7.0-pre

using the docker haskell image
expected path is:
["PATH=/root/.cabal/bin:/root/.local/bin:/opt/ghc/9.4.4/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","LANG=C.UTF-8"]
Tramp: Sending command ‘exec docker exec -it 
a896e8aacaf0adaedd49b9bcd6b8104d7b5c4343d08382b4912ffaa8192bbb81 /bin/sh  -i’
Tramp: Found remote shell prompt on 
‘a896e8aacaf0adaedd49b9bcd6b8104d7b5c4343d08382b4912ffaa8192bbb81’
path is: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin


You can see that crucial directories containing the Haskell compiler and
build tool are missing.

I'm not sure why they don't exist. But I can verify If I remove the
logic and preserve the non-existent directories things work:


(defun my/tramp-get-remote-path (vec)
  "Compile list of remote directories for PATH.
Nonexistent directories are removed from spec."
  (with-current-buffer (tramp-get-connection-buffer vec)
    ;; Expand connection-local variables.
    (tramp-set-connection-local-variables vec)
    (with-tramp-connection-property
        ;; When `tramp-own-remote-path' is in `tramp-remote-path', we
        ;; cache the result for the session only.  Otherwise, the
        ;; result is cached persistently.
        (if (memq 'tramp-own-remote-path tramp-remote-path)
            (tramp-get-process vec) vec)
        "remote-path"
      (let* ((remote-path (copy-tree tramp-remote-path))
             (elt1 (memq 'tramp-default-remote-path remote-path))
             (elt2 (memq 'tramp-own-remote-path remote-path))
             (default-remote-path
              (when elt1
                (or
                 (tramp-send-command-and-read
                  vec
                  (format
                   "echo \\\"`getconf PATH 2>%s`\\\""
                   (tramp-get-remote-null-device vec))
                  'noerror)
                 ;; Default if "getconf" is not available.
                 (progn
                   (tramp-message
                    vec 3
                    "`getconf PATH' not successful, using default value \"%s\"."
                    "/bin:/usr/bin")
                   "/bin:/usr/bin"))))
             (own-remote-path
              ;; The login shell could return more than just the $PATH
              ;; string.  So we use `tramp-end-of-heredoc' as marker.
              (when elt2
                (or
                 (tramp-send-command-and-read
                  vec
                  (format
                   "%s %s %s 'echo %s \\\"$PATH\\\"'"
                   (tramp-get-method-parameter vec 'tramp-remote-shell)
                   (string-join
                    (tramp-get-method-parameter vec 'tramp-remote-shell-login)
                    " ")
                   (string-join
                    (tramp-get-method-parameter vec 'tramp-remote-shell-args)
                    " ")
                   (tramp-shell-quote-argument tramp-end-of-heredoc))
                  'noerror (rx (literal tramp-end-of-heredoc)))
                 (progn
                   (tramp-message
                    vec 2 "Could not retrieve `tramp-own-remote-path'")
                   nil)))))


        ;; Replace place holder `tramp-default-remote-path'.
        (when elt1
          (setcdr elt1
                  (append
                   (split-string (or default-remote-path "") ":" 'omit)
                   (cdr elt1)))
          (setq remote-path (delq 'tramp-default-remote-path remote-path)))

        ;; Replace place holder `tramp-own-remote-path'.
        (when elt2
          (setcdr elt2
                  (append
                   (split-string (or own-remote-path "") ":" 'omit)
                   (cdr elt2)))
          (setq remote-path (delq 'tramp-own-remote-path remote-path)))

        ;; Remove double entries.
        (setq elt1 remote-path)
        (while (consp elt1)
          (while (and (car elt1) (setq elt2 (member (car elt1) (cdr elt1))))
            (setcar elt2 nil))
          (setq elt1 (cdr elt1)))

        (delq
         nil
         remote-path)))))

(advice-add 'tramp-get-remote-path :override 'my/tramp-get-remote-path)


I'm not sure if `Remove non-existing directories.` exists for a
particular reason or not, but I'd argue it's not appropriate for docker
containers.

I'm surprised that there aren't more bug reports along these lines since
this should affect anyone working with tramp, eshell, and eglot or
lsp-mode.

Thanks






reply via email to

[Prev in Thread] Current Thread [Next in Thread]