Skip to content

Commit

Permalink
new additional pyfilesystem implementation 'uftpmount' which mounts t…
Browse files Browse the repository at this point in the history
…he remote and accesses it via the local OSFS
  • Loading branch information
BerndSchuller committed Jun 19, 2024
1 parent 7fd879d commit b2d74a1
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Version 1.1.0 (mmm dd, 2024)
----------------------------
- API CHANGE: new Storage.put_file() method accepting
str-like or file-like data to upload to a remote destination
- new feature: new pyfilesystem implementation "uftpmount" which mounts
the remote directory and then accesses it via the local FS (OSFS)

Version 1.0.1 (Mar 22, 2024)
----------------------------
Expand Down
2 changes: 1 addition & 1 deletion pyunicore/uftp/uftpfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class UFTPFS(FTPFS):
Example: create with auth URL and username/password credentials
from pyunicore.credentials import UsernamePassword
from pyunicore.uftpfs import UFTPFS
from pyunicore.uftp.uftpfs import UFTPFS
auth = "https://localhost:9000/rest/auth/TEST
creds = UsernamePassword("demouser", "test123")
Expand Down
131 changes: 131 additions & 0 deletions pyunicore/uftp/uftpmountfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
from os import getenv

from fs.osfs import OSFS
from fs.opener import Opener

import pyunicore.credentials as uc_credentials
from pyunicore.uftp.uftp import UFTP

import subprocess


class UFTPMOUNTFS(OSFS):
"""A UFTP (UNICORE FTP) Filesystem which tries to mount the remote file system
and then access it locally.
This extends the fs.osfs.OSFS filesystem
with UFTP authentication.
Example: create with auth URL and username/password credentials
from pyunicore.credentials import UsernamePassword
from pyunicore.uftp.uftpmountfs import UFTPMOUNTFS
auth = "https://localhost:9000/rest/auth/TEST"
creds = UsernamePassword("demouser", "test123")
base_path = "/"
mount_dir = "/data/mount"
uftp_mount_fs = UFTPMOUNTFS(auth, creds, base_path, mount_dir)
uftp_mount_fs.tree()
Example: create with URL
from fs import open_fs
fs_url = "uftpmount://demouser:test123@localhost:9000/rest/auth/TEST:/opt/shared-data==/mnt"
uftp_fs = open_fs(fs_url)
uftp_fs.tree()
"""

def __init__(self, auth_url, creds, base_path="/", mount_dir="./uftp_mount"):
"""Creates a new UFTP FS instance authenticating using the given URL and credentials"""
self.host, self.port, uftp_password = UFTP().authenticate(creds, auth_url, base_path)
self.base_path = base_path
self.mount_dir = mount_dir
self._ensure_unmount(mount_dir)
print(self._run_fusedriver(self.host, self.port, uftp_password, self.mount_dir))
super().__init__(mount_dir)

def _ensure_unmount(self, mount_point):
"""
Unmounts the requested directory
"""
cmd = "fusermount -u '%s'" % mount_point
return self._run_command(cmd)

def _run_fusedriver(self, host, port, pwd, mount_point, debug=False):
cmds = [
"export UFTP_PASSWORD=%s" % pwd,
f"python3 -m pyunicore.uftp.uftpfuse {host}:{port} '{mount_point}'",
]
cmd = ""
for c in cmds:
cmd += c + "\n"
return self._run_command(cmd)

def _run_command(self, cmd):
try:
raw_output = subprocess.check_output(
cmd, shell=True, bufsize=4096, stderr=subprocess.STDOUT
)
exit_code = 0
except subprocess.CalledProcessError as cpe:
raw_output = cpe.output
exit_code = cpe.returncode
return exit_code, raw_output.decode("UTF-8")

def hassyspath(self, path):
return False

def __repr__(self):
return (
f"UFTPMOUNTFS({self.host!r}, port={self.port!r})"
", base_path={self.base_path!r}, mount_dir={self.mount_dir!r}"
)

__str__ = __repr__


class UFTPMountOpener(Opener):
"""Defines the Opener class used to open UFTP Mount FS instances based on a URL"""

protocols = ["uftpmount"]

def _parse(self, resource_url):
tok = resource_url.split("/rest/")
auth_url = "https://" + tok[0]
tok2 = tok[1].split(":")
auth_url = auth_url + "/rest/" + tok2[0]
base_dir = tok2[1] if len(tok) > 1 else "/"
tok3 = base_dir.split("==")
base_dir = tok3[0]
mount_dir = tok3[1] if len(tok3) > 1 else "/uftp_mount"
return auth_url, base_dir, mount_dir

def _read_token(self, token_spec):
token = None
if token_spec:
if token_spec.startswith("@@"):
env_name = token_spec[2:]
token = getenv(env_name, None)
elif token_spec.startswith("@"):
file_name = token_spec[1:]
with open(file_name) as f:
token = f.read().strip()
else:
token = token_spec
return token

def _create_credential(self, parse_result):
token = self._read_token(parse_result.params.get("token", None))
return uc_credentials.create_credential(
username=parse_result.username,
password=parse_result.password,
token=token,
identity=parse_result.params.get("identity", None),
)

def open_fs(self, fs_url, parse_result, writeable, create, cwd):
auth_url, base_dir, mount_dir = self._parse(parse_result.resource)
cred = self._create_credential(parse_result)
return UFTPMOUNTFS(auth_url, cred, base_dir, mount_dir)
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
entry_points={
"fs.opener": [
"uftp = pyunicore.uftp.uftpfs:UFTPOpener",
"uftpmount = pyunicore.uftp.uftpmountfs:UFTPMountOpener",
],
"console_scripts": [
"unicore-port-forwarder=pyunicore.forwarder:main",
Expand Down

0 comments on commit b2d74a1

Please sign in to comment.