heist/tool/path.py: clean_path() broken

clean_path() does not work when the cwd includes a symlink.

(Ok, granted, this is quite a non-standard situation for a Linux user. :-) In FreeBSD jails /home is a symlink pointing to /usr/home.

Setup:

  1. heist was installed via pip install heist-salt
  2. heist is available via $PATH
  3. On the remote host, /var/tmp/heist does not exist / has been removed.
  4. Work as salt-master in /home/salt-master, while /home is a symlink to /usr/home.
% heist --version
heist 7.0.2
% heist --log-level debug --aritfact-version 3006.7 salt.minion
[...]
[ERROR   ] Could not add aliases to PATH
[ERROR   ] Could not deploy the alias files from the path /home/salt-master/.heist/artifacts/onedir/linux/scripts/minion to the target
[ERROR   ] Creating and deploying the aliases failed. Will continue deploying
[ERROR   ] The path /home/salt-master/.heist/artifacts/onedir/linux/salt-3006.7-onedir-linux-x86_64.tar.xz is not in the correct directory
[INFO    ] [conn=0, chan=0] Starting SFTP put of /home/salt-master/.heist/artifacts/onedir/linux/salt-3006.7-onedir-linux-x86_64.tar.xz,/home/salt-master/.heist/artifacts/onedir/linux/node3.sz9i.net/root_dir,/home/salt-master/.heist/artifacts/onedir/linux/node3.sz9i.net/code_checksum to /var/tmp/heist_root/9276
[DEBUG   ] return value: {'result': 'Error', 'comment': "OS error: [Errno 2] No such file or directory: b'/home/salt-master/.heist/artifacts/onedir/linux/node3.sz9i.net/code_checksum'", 'retvalue': 1}
 result: Error
 comment: OS error: [Errno 2] No such file or directory: b'/home/salt-master/.heist/artifacts/onedir/linux/node3.sz9i.net/code_checksum'
 retvalue: 1

[ERROR   ] Could not add aliases to PATH
[ERROR   ] Could not deploy the alias files from the path /home/salt-master/.heist/artifacts/onedir/linux/scripts/minion to the target
[ERROR   ] Creating and deploying the aliases failed. Will continue deploying
[ERROR   ] The path /home/salt-master/.heist/artifacts/onedir/linux/salt-3006.7-onedir-linux-x86_64.tar.xz is not in the correct directory

After some debugging with pdb, it seems the core issue lies here: https://gitlab.com/saltstack/pop/heist/-/blob/df8331e543ee098dd2874958af5c2e2804924da2/heist/tool/path.py#L93

 78 def clean_path(hub, root, path, subdir=False):
 79     """
 80     Return a clean path that has been validated.
 81     Using os.path here instead of pathlib, because
 82     the api's functionalities are not entirely the
 83     same yet.
 84     """

        # import pdb; pdb.set_trace()
        # root is '/home/salt-master/.heist/artifacts/onedir/linux'
        # path is '/home/salt-master/.heist/artifacts/onedir/linux/host.example.test/code_checksum'

 85     real_root = _realpath(root)            # real_root is '/usr/home/salt-master/.heist/artifacts/onedir/linux'
 86     path = os.path.expandvars(path)        # path is (still) '/home/salt-master/.heist/artifacts/onedir/linux/host.example.test/code_checksum'
 87     if not os.path.isabs(real_root):       # os.path.isabs(real_root) => True
 88         return ""
 89     if not os.path.isabs(path):            # os.path.isabs(path) => True
 90         path = os.path.join(root, path)
 91     path = os.path.normpath(path)          # path is (still) '/home/salt-master/.heist/artifacts/onedir/linux/host.example.test/code_checksum'
 92     real_path = _realpath(path)            # real_path is '/usr/home/salt-master/.heist/artifacts/onedir/linux/host.example.test/code_checksum'
 93     if path.startswith(real_root):         # path.startswith(real_root) => False
                                               # real_path.startswith(real_root) => True
                                               # So, should `path` be changed to `real_path` here?
 94         return os.path.join(root, path)
 95     if subdir: 
 96         if real_path.startswith(real_root):
 97             return real_path
 98     else:
            # os.path.dirname(real_path) is  '/usr/home/salt-master/.heist/artifacts/onedir/linux/host.example.test'
            # os.path.normpath(real_root) is '/usr/home/salt-master/.heist/artifacts/onedir/linux'
 99         if os.path.dirname(real_path) == os.path.normpath(real_root):
100             return real_path
101     return ""

I propose this change: !153

I tested this on FreeBSD 14 and this patch brings me past OS error: [Errno 2] No such file or directory: b'/home/salt-master/.heist/artifacts/onedir/linux/node3.sz9i.net/code_checksum'. :-)


I found a related issue in the method which calls clean_path(): heist-salt!93

Edited by Alex W.