Loading services/webapp/code/rosetta/core_app/api.py +28 −6 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ from rest_framework import status, serializers, viewsets from rest_framework.views import APIView from .utils import format_exception, send_email, os_shell, now_t, get_ssh_access_mode_credentials, get_or_create_container_from_repository, booleanize from .models import Profile, Task, TaskStatuses, Computing, Storage, KeyPair from .exceptions import ConsistencyException from .exceptions import PermissionDenied import json # Setup logging Loading Loading @@ -153,6 +153,10 @@ class PrivatePOSTAPI(APIView): # Call API logic return self._post(request) except Exception as e: # TODO: refactor me if isinstance(e, PermissionDenied): return error400(format(e)) else: logger.error(format_exception(e)) return error500('Got error in processing request: {}'.format(e)) Loading @@ -174,11 +178,14 @@ class PrivateGETAPI(APIView): # Call API logic return self._get(request) except Exception as e: # TODO: refactor me if isinstance(e, PermissionDenied): return error400(format(e)) else: logger.error(format_exception(e)) return error500('Got error in processing request: {}'.format(e)) #============================== # User & profile APIs #============================== Loading Loading @@ -689,6 +696,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI): def delete(self, path, user, storage): if storage.read_only: raise PermissionDenied('This storage is read-only') if storage.type == 'generic_posix': shell_path = self.sanitize_and_prepare_shell_path(path, user, storage) Loading @@ -707,6 +717,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI): def mkdir(self, path, user, storage, force=False): if storage.read_only: raise PermissionDenied('This storage is read-only') path = self.sanitize_and_prepare_shell_path(path, user, storage) if storage.type == 'generic_posix': Loading Loading @@ -751,6 +764,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI): def rename(self, old, new, user, storage): if storage.read_only: raise PermissionDenied('This storage is read-only') if storage.type == 'generic_posix': old = self.sanitize_and_prepare_shell_path(old, user, storage) Loading @@ -772,6 +788,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI): def copy(self, source, target, user, storage): if storage.read_only: raise PermissionDenied('This storage is read-only') if storage.type == 'generic_posix': source = self.sanitize_and_prepare_shell_path(source, user, storage) Loading Loading @@ -805,6 +824,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI): def scp_to(self, source, target, user, storage, mode='get'): if storage.read_only: raise PermissionDenied('This storage is read-only') source = self.sanitize_shell_path(source) # This is a folder on Rosetta (/tmp) target = self.sanitize_and_prepare_shell_path(target, user, storage) Loading services/webapp/code/rosetta/core_app/computing_managers.py +16 −4 Original line number Diff line number Diff line Loading @@ -147,11 +147,17 @@ class InternalStandaloneComputingManager(StandaloneComputingManager): if '$USER' in expanded_bind_path: expanded_bind_path = expanded_bind_path.replace('$USER', task.user.username) # Read only? if storage.read_only: mode_string = ':ro' else: mode_string = '' # Add the bind if not binds: binds = '-v{}:{}'.format(expanded_base_path, expanded_bind_path) binds = '-v{}:{}{}'.format(expanded_base_path, expanded_bind_path, mode_string) else: binds += ' -v{}:{}'.format(expanded_base_path, expanded_bind_path) binds += ' -v{}:{}{}'.format(expanded_base_path, expanded_bind_path, mode_string) # Host name, image entry command run_command += ' {} -h task-{} --name task-{} -d -t {}/{}:{}'.format(binds, task.short_uuid, task.short_uuid, task.container.registry, task.container.image_name, task.container.image_tag) Loading Loading @@ -348,11 +354,17 @@ class SSHStandaloneComputingManager(StandaloneComputingManager, SSHComputingMana if '$USER' in expanded_bind_path: expanded_bind_path = expanded_bind_path.replace('$USER', task.user.username) # Read only? if storage.read_only: mode_string = ':ro' else: mode_string = '' # Add the bind if not binds: binds = '-v{}:{}'.format(expanded_base_path, expanded_bind_path) binds = '-v{}:{}{}'.format(expanded_base_path, expanded_bind_path, mode_string) else: binds += ' -v{}:{}'.format(expanded_base_path, expanded_bind_path) binds += ' -v{}:{}{}'.format(expanded_base_path, expanded_bind_path, mode_string) # TODO: remove this hardcoding prefix = 'sudo' if (computing_host == 'slurmclusterworker' and container_engine=='docker') else '' Loading services/webapp/code/rosetta/core_app/exceptions.py +3 −0 Original line number Diff line number Diff line Loading @@ -4,3 +4,6 @@ class ErrorMessage(Exception): class ConsistencyException(Exception): pass class PermissionDenied(Exception): pass No newline at end of file services/webapp/code/rosetta/core_app/migrations/0037_storage_read_only.py 0 → 100644 +18 −0 Original line number Diff line number Diff line # Generated by Django 2.2.1 on 2025-03-05 09:46 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('core_app', '0036_container_disable_http_basicauth_embedding'), ] operations = [ migrations.AddField( model_name='storage', name='read_only', field=models.BooleanField(default=False, verbose_name='Read only? (if supported)'), ), ] services/webapp/code/rosetta/core_app/models.py +3 −0 Original line number Diff line number Diff line Loading @@ -387,6 +387,9 @@ class Storage(models.Model): base_path = models.CharField('Base path', max_length=4096, blank=False, null=False) bind_path = models.CharField('Bind path', max_length=4096, blank=True, null=True) # Read only? read_only = models.BooleanField('Read only? (if supported)', default=False) # Link with a computing resource computing = models.ForeignKey(Computing, related_name='storages', on_delete=models.CASCADE, blank=True, null=True) # Make optional? access_through_computing = models.BooleanField('Access through linked computing resource?', default=False) Loading Loading
services/webapp/code/rosetta/core_app/api.py +28 −6 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ from rest_framework import status, serializers, viewsets from rest_framework.views import APIView from .utils import format_exception, send_email, os_shell, now_t, get_ssh_access_mode_credentials, get_or_create_container_from_repository, booleanize from .models import Profile, Task, TaskStatuses, Computing, Storage, KeyPair from .exceptions import ConsistencyException from .exceptions import PermissionDenied import json # Setup logging Loading Loading @@ -153,6 +153,10 @@ class PrivatePOSTAPI(APIView): # Call API logic return self._post(request) except Exception as e: # TODO: refactor me if isinstance(e, PermissionDenied): return error400(format(e)) else: logger.error(format_exception(e)) return error500('Got error in processing request: {}'.format(e)) Loading @@ -174,11 +178,14 @@ class PrivateGETAPI(APIView): # Call API logic return self._get(request) except Exception as e: # TODO: refactor me if isinstance(e, PermissionDenied): return error400(format(e)) else: logger.error(format_exception(e)) return error500('Got error in processing request: {}'.format(e)) #============================== # User & profile APIs #============================== Loading Loading @@ -689,6 +696,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI): def delete(self, path, user, storage): if storage.read_only: raise PermissionDenied('This storage is read-only') if storage.type == 'generic_posix': shell_path = self.sanitize_and_prepare_shell_path(path, user, storage) Loading @@ -707,6 +717,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI): def mkdir(self, path, user, storage, force=False): if storage.read_only: raise PermissionDenied('This storage is read-only') path = self.sanitize_and_prepare_shell_path(path, user, storage) if storage.type == 'generic_posix': Loading Loading @@ -751,6 +764,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI): def rename(self, old, new, user, storage): if storage.read_only: raise PermissionDenied('This storage is read-only') if storage.type == 'generic_posix': old = self.sanitize_and_prepare_shell_path(old, user, storage) Loading @@ -772,6 +788,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI): def copy(self, source, target, user, storage): if storage.read_only: raise PermissionDenied('This storage is read-only') if storage.type == 'generic_posix': source = self.sanitize_and_prepare_shell_path(source, user, storage) Loading Loading @@ -805,6 +824,9 @@ class FileManagerAPI(PrivateGETAPI, PrivatePOSTAPI): def scp_to(self, source, target, user, storage, mode='get'): if storage.read_only: raise PermissionDenied('This storage is read-only') source = self.sanitize_shell_path(source) # This is a folder on Rosetta (/tmp) target = self.sanitize_and_prepare_shell_path(target, user, storage) Loading
services/webapp/code/rosetta/core_app/computing_managers.py +16 −4 Original line number Diff line number Diff line Loading @@ -147,11 +147,17 @@ class InternalStandaloneComputingManager(StandaloneComputingManager): if '$USER' in expanded_bind_path: expanded_bind_path = expanded_bind_path.replace('$USER', task.user.username) # Read only? if storage.read_only: mode_string = ':ro' else: mode_string = '' # Add the bind if not binds: binds = '-v{}:{}'.format(expanded_base_path, expanded_bind_path) binds = '-v{}:{}{}'.format(expanded_base_path, expanded_bind_path, mode_string) else: binds += ' -v{}:{}'.format(expanded_base_path, expanded_bind_path) binds += ' -v{}:{}{}'.format(expanded_base_path, expanded_bind_path, mode_string) # Host name, image entry command run_command += ' {} -h task-{} --name task-{} -d -t {}/{}:{}'.format(binds, task.short_uuid, task.short_uuid, task.container.registry, task.container.image_name, task.container.image_tag) Loading Loading @@ -348,11 +354,17 @@ class SSHStandaloneComputingManager(StandaloneComputingManager, SSHComputingMana if '$USER' in expanded_bind_path: expanded_bind_path = expanded_bind_path.replace('$USER', task.user.username) # Read only? if storage.read_only: mode_string = ':ro' else: mode_string = '' # Add the bind if not binds: binds = '-v{}:{}'.format(expanded_base_path, expanded_bind_path) binds = '-v{}:{}{}'.format(expanded_base_path, expanded_bind_path, mode_string) else: binds += ' -v{}:{}'.format(expanded_base_path, expanded_bind_path) binds += ' -v{}:{}{}'.format(expanded_base_path, expanded_bind_path, mode_string) # TODO: remove this hardcoding prefix = 'sudo' if (computing_host == 'slurmclusterworker' and container_engine=='docker') else '' Loading
services/webapp/code/rosetta/core_app/exceptions.py +3 −0 Original line number Diff line number Diff line Loading @@ -4,3 +4,6 @@ class ErrorMessage(Exception): class ConsistencyException(Exception): pass class PermissionDenied(Exception): pass No newline at end of file
services/webapp/code/rosetta/core_app/migrations/0037_storage_read_only.py 0 → 100644 +18 −0 Original line number Diff line number Diff line # Generated by Django 2.2.1 on 2025-03-05 09:46 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('core_app', '0036_container_disable_http_basicauth_embedding'), ] operations = [ migrations.AddField( model_name='storage', name='read_only', field=models.BooleanField(default=False, verbose_name='Read only? (if supported)'), ), ]
services/webapp/code/rosetta/core_app/models.py +3 −0 Original line number Diff line number Diff line Loading @@ -387,6 +387,9 @@ class Storage(models.Model): base_path = models.CharField('Base path', max_length=4096, blank=False, null=False) bind_path = models.CharField('Bind path', max_length=4096, blank=True, null=True) # Read only? read_only = models.BooleanField('Read only? (if supported)', default=False) # Link with a computing resource computing = models.ForeignKey(Computing, related_name='storages', on_delete=models.CASCADE, blank=True, null=True) # Make optional? access_through_computing = models.BooleanField('Access through linked computing resource?', default=False) Loading