def testYAMLAliases(self):
        folderModel = Folder()
        aliasedFolders = list(folderModel.find({'name': 'Common'}, force=True))
        self.assertTrue(len(aliasedFolders) == 2)

        for folder in aliasedFolders:
            self.assertTrue(
                len(list(folderModel.childItems(folder, force=True))) == 2)
Exemple #2
0
    def testYAMLAliases(self):
        folderModel = Folder()
        aliasedFolders = list(folderModel.find({'name': 'Common'}, force=True))
        self.assertTrue(len(aliasedFolders) == 2)

        for folder in aliasedFolders:
            self.assertTrue(
                len(list(folderModel.childItems(folder, force=True))) == 2
            )
    def getAdjacentImages(self, currentImage, currentFolder=None):
        folderModel = Folder()
        if currentFolder:
            folder = currentFolder
        else:
            folder = folderModel.load(
                currentImage['folderId'], user=self.getCurrentUser(), level=AccessType.READ)

        if folder.get('isVirtual'):
            children = folderModel.childItems(folder, includeVirtual=True)
        else:
            children = folderModel.childItems(folder)

        allImages = [item for item in children if _isLargeImageItem(item)]
        try:
            index = allImages.index(currentImage)
        except ValueError:
            raise RestException('Id is not an image', 404)

        return {
            'previous': allImages[index - 1],
            'next': allImages[(index + 1) % len(allImages)]
        }
    def getAdjacentImages(self, currentImage):
        folderModel = Folder()
        folder = folderModel.load(currentImage['folderId'],
                                  user=self.getCurrentUser(),
                                  level=AccessType.READ)
        allImages = [
            item for item in folderModel.childItems(folder)
            if _isLargeImageItem(item)
        ]
        try:
            index = allImages.index(currentImage)
        except ValueError:
            raise RestException('Id is not an image', 404)

        return {
            'previous': allImages[index - 1],
            'next': allImages[(index + 1) % len(allImages)]
        }
    def _syncItems(image, cli_dict, user):
        folderModel = Folder()
        itemModel = Item()

        children = folderModel.childItems(
            image.tagFolder, filters={'meta.slicerCLIType': 'task'})
        existingItems = {item['name']: item for item in children}

        for cli, desc in six.iteritems(cli_dict):
            item = itemModel.createItem(
                cli,
                user,
                image.tagFolder,
                'Slicer CLI generated CLI command item',
                reuseExisting=True)
            meta_data = dict(slicerCLIType='task',
                             type=desc.get('type', 'Unknown'))

            # copy some things from the image to be independent
            meta_data['image'] = image.name
            meta_data[
                'digest'] = image.digest if image.name != image.digest else None

            desc_type = desc.get('desc-type', 'xml')

            if desc_type == 'xml':
                meta_data.update(parse_xml_desc(item, desc, user))
            elif desc_type == 'yaml':
                meta_data.update(parse_yaml_desc(item, desc, user))
            elif desc_type == 'json':
                meta_data.update(parse_json_desc(item, desc, user))

            itemModel.setMetadata(item, meta_data)

            if cli in existingItems:
                del existingItems[cli]

        # delete superfluous items
        for item in six.itervalues(existingItems):
            itemModel.remove(item)
Exemple #6
0
class App(Resource):
    def __init__(self):
        super(App, self).__init__()
        self.resourceName = 'app'
        self._model = Folder()

        self.route('POST', (), self.initApp)
        self.route('GET', (), self.listApp)
        self.route('DELETE', (':app_id', ), self.deleteApp)
        self.route('POST', (':app_id', 'release'), self.createNewRelease)
        self.route('GET', (':app_id', 'release'), self.getAllReleases)
        self.route('GET', (':app_id', 'release', ':release_id_or_name'),
                   self.getReleaseByIdOrName)
        self.route('DELETE', (':app_id', 'release', ':release_id_or_name'),
                   self.deleteReleaseByIdOrName)
        self.route('GET', (':app_id', 'extension'), self.getExtensions)
        self.route('GET', (':app_id', 'extension', ':extension_name'),
                   self.getExtensionByName)
        self.route('POST', (':app_id', 'extension'),
                   self.createOrUpdateExtension)
        self.route('DELETE', (':app_id', 'extension', ':ext_id'),
                   self.deleteExtension)

    @autoDescribeRoute(
        Description('Create a new application.').responseClass('Folder').notes(
            'If collectionId is missing or collectionName does not match an existing '
            'collection, a fresh new collection will be created with the "collection_name" '
            'given in parameters. '
            'By default the name "Applications" will be given to the collection.'
        ).param('name', 'The name of the application.').param(
            'app_description', 'Application description.',
            required=False).param(
                'collection_id',
                'The ID of the collection which contain the application',
                required=False).param(
                    'collection_name',
                    'The Name of the collection which be created to contain'
                    ' the application',
                    required=False).param('collection_description',
                                          'Collection description.',
                                          required=False).
        param('public',
              'Whether the collection should be publicly visible.',
              required=False,
              dataType='boolean',
              default=True).errorResponse(
                  'Write permission denied on the application.', 403))
    @access.user(scope=TokenScope.DATA_WRITE)
    def initApp(self, name, app_description, collection_id, collection_name,
                collection_description, public):
        """
        Create the directory for start a new application. By default, without specifying
        a ``collection_id``, it will create a new collection name either ``collection_name``
        if provided, or **'Applications'**. If the collection 'Applications already exist it will
        get it.
        Return the new application (as a folder) that always contain a default
        sub-folder named 'nightly'.

        :param name: Name of the new application
        :param app_description: Description of the new application
        :param collection_id: Id of the collection within create the application
        :param collection_name: Name of the collection which will be created
        :param collection_description: Description of the new collection
        :param public: Whether the new collection should be publicly visible
        :return: The new application folder
        """
        creator = self.getCurrentUser()
        # Load or create the collection that contain the application
        if collection_id:
            collection = Collection().load(collection_id, force=True)
        elif collection_name:
            collection = list(Collection().find({'name': collection_name},
                                                user=creator))
            if not collection:
                collection = Collection().createCollection(
                    name=collection_name,
                    description=collection_description,
                    public=public,
                    creator=creator)
            else:
                collection = collection[0]
        else:
            collection = list(Collection().find({'name': 'Applications'},
                                                user=creator))
            if not collection:
                collection = Collection().createCollection(
                    name='Applications',
                    description=collection_description,
                    public=public,
                    creator=creator)
            else:
                collection = collection[0]
        # Create the application
        if not app_description:
            app_description = ''
        app = self._model.createFolder(parent=collection,
                                       name=name,
                                       description=app_description,
                                       parentType='Collection',
                                       public=public,
                                       creator=creator)
        # Create the 'nightly' release which will be the default folder when uploading an extension
        self._model.createFolder(
            parent=app,
            name=constants.NIGHTLY_RELEASE_NAME,
            description='Uploaded each night, always up-to-date',
            parentType='Folder',
            public=public,
            creator=creator)
        # Set a default template name for extensions in the application,
        # this can be changed in anytime.
        return self._model.setMetadata(
            app, {
                'extensionNameTemplate':
                '{app_revision}_{os}_{arch}_{baseName}_{revision}'
            })

    @autoDescribeRoute(
        Description('List existing application.').responseClass(
            'Folder', array=True).param('app_id',
                                        'The ID of the application.',
                                        required=False).param(
                                            'collection_id',
                                            'The ID of the collection.',
                                            required=False).param(
                                                'name',
                                                'The name of the application.',
                                                required=False).
        param('text',
              'Provide text search of the application.',
              required=False).pagingParams(
                  defaultSort='name').errorResponse().errorResponse(
                      'Read permission denied on the application.', 403))
    @access.user(scope=TokenScope.DATA_READ)
    def listApp(self, app_id, collection_id, name, text, limit, offset, sort):
        """
        List existing applications base on some optional parameters:

        :param app_id: Application ID
        :param collection_id: Collection ID
        :param name: Name of the application
        :param text: Provide text search on the name of the application
        :return: List of applications
        """
        user = self.getCurrentUser()

        if ObjectId.is_valid(app_id):
            return self._model.load(app_id, user=user)
        else:
            if collection_id:
                parent = Collection().load(collection_id,
                                           user=user,
                                           level=AccessType.READ,
                                           exc=True)
            else:
                parent = Collection().findOne(query={'name': 'Applications'},
                                              user=user,
                                              offset=offset)
            if parent:
                filters = {}
                if text:
                    filters['$text'] = {'$search': text}
                if name:
                    filters['name'] = name

                return list(
                    self._model.childFolders(parentType='collection',
                                             parent=parent,
                                             user=user,
                                             offset=offset,
                                             limit=limit,
                                             sort=sort,
                                             filters=filters))
            return []

    @access.user(scope=TokenScope.DATA_WRITE)
    @autoDescribeRoute(
        Description('Delete an Application by ID.').modelParam(
            'app_id', model=Folder, level=AccessType.ADMIN).param(
                'progress',
                'Whether to record progress on this task.',
                required=False,
                dataType='boolean',
                default=False).errorResponse('ID was invalid.').errorResponse(
                    'Admin access was denied for the application.', 403))
    def deleteApp(self, folder, progress):
        """
        Delete the application by ID.

        :param id: Id of the application
        :return: Confirmation message with the deleted application name
        """
        return _deleteFolder(folder, progress, self.getCurrentUser())

    @autoDescribeRoute(
        Description('Create a new release.').responseClass('Folder').notes(
            'The application\'s revision is stored as metadata of the new release.'
        ).param('name', 'The release\'s name.').param(
            'app_id', 'The application\'s ID which contain the release').param(
                'app_revision',
                'The application\'s revision which correspond to the release'
            ).param('description',
                    'The application\'s description.',
                    required=False).param(
                        'public',
                        'Whether the release should be publicly visible.',
                        required=False,
                        dataType='boolean',
                        default=True).errorResponse())
    @access.user(scope=TokenScope.DATA_WRITE)
    def createNewRelease(self, name, app_id, app_revision, description,
                         public):
        """
        Create a new release with the ``name`` within the application. The ``app_revision``
        will permit to automatically choose this release when uploading an extension
        with a matching `app_revision`` metadata.

        :param name: Name of the new release
        :param app_id: Application ID
        :param app_revision: Revision of the application corresponding to this release
        :param description: Description of the new release
        :param public: Whether the release should be publicly visible

        :return: The new release folder
        """
        creator = self.getCurrentUser()
        application = self._model.load(app_id, user=creator)
        if not description:
            description = ''
        release = self._model.createFolder(parent=application,
                                           name=name,
                                           description=description,
                                           parentType='Folder',
                                           public=public,
                                           creator=creator)

        return self._model.setMetadata(release, {'revision': app_revision})

    @autoDescribeRoute(
        Description('Get all the releases from an application.').responseClass(
            'Folder').param('app_id', 'The application\'s ID.').pagingParams(
                defaultSort='name').errorResponse(
                    'ID was invalid.').errorResponse(
                        'Read permission denied on the application.', 403))
    @access.user(scope=TokenScope.DATA_READ)
    def getAllReleases(self, app_id, limit, offset, sort):
        """
        Get a list of all the release of an application.

        :param app_id: Application ID
        :return: List of all release within the application
        """
        user = self.getCurrentUser()
        application = self._model.load(app_id, user=user)
        # It returns the nightly release too
        return list(
            self._model.childFolders(application,
                                     'Folder',
                                     user=user,
                                     limit=limit,
                                     offset=offset,
                                     sort=sort))

    @autoDescribeRoute(
        Description(
            'Get a particular releases by ID or name from an application.').
        responseClass('Folder').param(
            'app_id', 'The application\'s ID.').param(
                'release_id_or_name',
                'The release\'s ID or name.').errorResponse(
                    'ID or name was invalid.').errorResponse(
                        'Read permission denied on the application.', 403))
    @access.user(scope=TokenScope.DATA_READ)
    def getReleaseByIdOrName(self, app_id, release_id_or_name):
        """
        Get the release folder by ID or by name.

        :param app_id: Application ID
        :param release_id_or_name: Could be either the release ID or the release name
        :return: The release folder
        """
        user = self.getCurrentUser()
        application = self._model.load(app_id, user=user)

        if ObjectId.is_valid(release_id_or_name):
            return self._model.load(release_id_or_name, user=user)
        release_folder = list(
            self._model.childFolders(
                application,
                'Folder',
                filters={'lowerName': release_id_or_name.lower()}))
        if not release_folder:
            return None
        return release_folder[0]

    @access.user(scope=TokenScope.DATA_WRITE)
    @autoDescribeRoute(
        Description('Delete a release by ID or name.').modelParam(
            'app_id', model=Folder,
            level=AccessType.ADMIN).param('release_id_or_name',
                                          'The release\'s ID or name.').
        param('progress',
              'Whether to record progress on this task.',
              required=False,
              dataType='boolean',
              default=False).errorResponse('ID was invalid.').errorResponse(
                  'Admin access was denied for the release.', 403))
    def deleteReleaseByIdOrName(self, folder, release_id_or_name, progress):
        """
        Delete a release by ID or name.

        :param app_id: Application ID
        :param release_id_or_name: Could be either the release ID or the release name
        :param progress: Whether to record progress on this task
        :return: Confirmation message with the deleted release name
        """
        user = self.getCurrentUser()

        if ObjectId.is_valid(release_id_or_name):
            release = self._model.load(release_id_or_name, user=user)
        else:
            release_folder = list(
                self._model.childFolders(
                    folder,
                    'Folder',
                    filters={'lowerName': release_id_or_name.lower()}))
            if not release_folder:
                raise Exception("Couldn't find release %s" %
                                release_id_or_name)
            release = release_folder[0]

        return _deleteFolder(release, progress, self.getCurrentUser())

    @autoDescribeRoute(
        Description('List or search available extensions.').responseClass(
            'Extension').param('app_id', 'The ID of the application.').param(
                'release_id', 'The release id.', required=False).param(
                    'extension_id', 'The extension id.', required=False).param(
                        'os',
                        'The target operating system of the package.',
                        required=False,
                        enum=['linux', 'win',
                              'macosx']).param('arch',
                                               'The os chip architecture.',
                                               required=False,
                                               enum=['i386', 'amd64']).
        param('app_revision', 'The revision of the package.',
              required=False).param(
                  'search',
                  'Text matched against extension name or description.',
                  required=False).pagingParams(
                      defaultSort='created').errorResponse())
    @access.cookie
    @access.public
    def getExtensions(self, app_id, release_id, extension_id, os, arch,
                      app_revision, search, limit, offset, sort):
        """
        Get a list of extension which is filtered by some optional parameters:

        :param app_id: Application ID
        :param release_id: Release ID
        :param extension_id: Extension ID
        :param os: The operation system used for the extension.
        :param arch: The architecture compatible with the extension.
        :param app_revision: The revision of the application
        :param search: Text search on the name of the extension
        :return: The list of extensions
        """
        filters = {
            '$and': [{
                'meta.app_id': {
                    '$eq': app_id
                }
            }, {
                'meta.os': {
                    '$exists': True
                }
            }, {
                'meta.arch': {
                    '$exists': True
                }
            }, {
                'meta.revision': {
                    '$exists': True
                }
            }]
        }
        if ObjectId.is_valid(extension_id):
            filters['_id'] = ObjectId(extension_id)
        if ObjectId.is_valid(release_id):
            filters['folderId'] = ObjectId(release_id)
        if os:
            filters['meta.os'] = os
        if arch:
            filters['meta.arch'] = arch
        if app_revision:
            filters['meta.revision'] = app_revision
        if search:
            # Provide a full text search on baseName
            filters['meta.baseName'] = search

        return list(ExtensionModel().find(query=filters,
                                          limit=limit,
                                          offset=offset,
                                          sort=sort))

    @autoDescribeRoute(
        Description('Get a particular extension by name from an application.').
        responseClass('Item').param('app_id', 'The application\'s ID.').param(
            'extension_name', 'The extension\'s name.').errorResponse(
                'ID or name was invalid.').errorResponse(
                    'Read permission denied on the application.', 403))
    @access.user(scope=TokenScope.DATA_READ)
    def getExtensionByName(self, app_id, extension_name):
        """
        Get the extension item by name.

        :param app_id: Application ID
        :param extension_name: The extension name
        :return: The extension item
        """
        user = self.getCurrentUser()
        application = self._model.load(app_id, user=user)

        release_folder = list(self._model.childFolders(application, 'Folder'))
        if not release_folder:
            raise Exception('The application has no release')
        for release in release_folder:
            extensions = list(
                self._model.childItems(
                    release, filters={'lowerName': extension_name.lower()}))
            if extensions:
                return extensions[0]
        return None

    @autoDescribeRoute(  # noqa: C901
        Description('Create or Update an extension package.').param(
            'app_id', 'The ID of the App.').param(
                'os',
                'The target operating system of the package.',
                enum=['linux', 'win',
                      'macosx']).param('arch',
                                       'The os chip architecture.',
                                       enum=['i386', 'amd64']).
        param('baseName',
              'The baseName of the package (ie installer baseName).').param(
                  'repository_type',
                  'The type of the repository (svn, git).').param(
                      'repository_url', 'The url of the repository.').param(
                          'revision',
                          'The svn or git revision of the extension.').param(
                              'app_revision',
                              'The revision of the application '
                              'that the extension was built against.').param(
                                  'packagetype',
                                  'Installer, data, etc.').param(
                                      'codebase',
                                      'The codebase baseName (Ex: Slicer4).').
        param('description', 'Text describing the extension.').param(
            'release',
            'Release identifier (Ex: 0.0.1, 0.0.2, 0.1).',
            required=False).param(
                'icon_url',
                'The url of the icon for the extension.',
                required=False).param(
                    'development_status',
                    'Arbitrary description of the status of the extension '
                    '(stable, active, etc).',
                    required=False).
        param(
            'category',
            'Category under which to place the extension. Subcategories should be '
            'delimited by character. If none is passed, will render under '
            'the Miscellaneous category..',
            required=False
        ).param(
            'enabled',
            'Boolean indicating if the extension should be automatically enabled '
            'after its installation.',
            required=False).param('homepage',
                                  'The url of the extension homepage.',
                                  required=False).
        param('screenshots',
              'Space-separate list of URLs of screenshots for the extension.',
              required=False).param('contributors',
                                    'List of contributors of the extension',
                                    required=False).errorResponse())
    @access.cookie
    @access.public
    def createOrUpdateExtension(self, app_id, os, arch, baseName,
                                repository_type, repository_url, revision,
                                app_revision, packagetype, codebase,
                                description, release, icon_url,
                                development_status, category, enabled,
                                homepage, screenshots, contributors):
        """
        Upload an extension package in the database, in a specific release with providing
        ``release_id``. Or by default in the **'Nightly'** folder.

        :param app_id: The ID of the application.
        :param os: The operation system used for the extension.
        :param arch: The architecture compatible with the extension.
        :param baseName: The base name of the extension.
        :param repository_type: The type of repository (github, gitlab, ...).
        :param repository_url: The Url of the repository.
        :param revision: The revision of the extension.
        :param app_revision: The revision of the application.
        :param packagetype: Type of the extension.
        :param codebase: The codebase baseName.
        :param description: The description of the extension.
        :return: The status of the upload.
        """
        creator = self.getCurrentUser()
        application = self._model.load(app_id, user=creator)
        release_folder = None
        # Find the release by metadata revision
        releases = self._model.childFolders(application,
                                            'Folder',
                                            user=creator)
        for folder in releases:
            if 'meta' in folder:
                if folder['meta']['revision'] == app_revision:
                    release_folder = folder
                    break
        if not release_folder:
            # Only the nightly folder in the list
            release_folder = list(
                self._model.childFolders(
                    application,
                    'Folder',
                    user=creator,
                    filters={'name': constants.NIGHTLY_RELEASE_NAME}))
            if not release_folder:
                raise Exception('The %s folder not found.' %
                                constants.NIGHTLY_RELEASE_NAME)
            release_folder = release_folder[0]

        params = {
            'app_id': app_id,
            'baseName': baseName,
            'os': os,
            'arch': arch,
            'repository_type': repository_type,
            'repository_url': repository_url,
            'revision': revision,
            'app_revision': app_revision,
            'packagetype': packagetype,
            'codebase': codebase,
            'description': description
        }
        if release:
            params['release'] = release
        if icon_url:
            params['icon_url'] = icon_url
        if development_status:
            params['development_status'] = development_status
        if category:
            params['category'] = category
        if enabled:
            params['enabled'] = enabled
        if homepage:
            params['homepage'] = homepage
        if screenshots:
            params['screenshots'] = screenshots
        if contributors:
            params['contributors'] = contributors

        name = application['meta']['extensionNameTemplate'].format(**params)
        filters = {'name': name}
        # Only one extensions should be in this list
        extensions = list(ExtensionModel().get(release_folder,
                                               filters=filters))
        if not len(extensions):
            # The extension doesn't exist yet:
            extension = ExtensionModel().createExtension(
                name, creator, release_folder, params)
        elif len(extensions) == 1:
            extension = extensions[0]
        else:
            raise Exception(
                'Too many extensions found for the same name :"%s"' % name)

        # Check the file inside the extension Item
        files = Item().childFiles(extension)
        if files.count() == 1:
            old_file = files.next()
            # catch the event of upload success and remove the file
            events.bind('model.file.finalizeUpload.after', 'application',
                        File().remove(old_file))
        elif not files.count():
            # Extension new or empty
            pass
        else:
            raise Exception("More than 1 binary file in the extension.")

        old_meta = {
            'baseName': extension['meta']['baseName'],
            'os': extension['meta']['os'],
            'arch': extension['meta']['arch'],
            'revision': extension['meta']['revision'],
            'app_revision': extension['meta']['app_revision']
        }
        identifier_meta = {
            'baseName': baseName,
            'os': os,
            'arch': arch,
            'revision': revision,
            'app_revision': app_revision
        }
        if identifier_meta == old_meta and len(extensions):
            # The revision is the same than these before, no need to upload
            extension = ExtensionModel().setMetadata(extension, params)
            events.unbind('model.file.finalizeUpload.after', 'application')

        # Ready to upload the binary file
        return extension

    @access.user(scope=TokenScope.DATA_WRITE)
    @autoDescribeRoute(
        Description('Delete an Extension by ID.').param(
            'app_id', 'The ID of the App.').modelParam(
                'ext_id', model=Item, level=AccessType.WRITE).errorResponse(
                    'ID was invalid.').errorResponse(
                        'Admin access was denied for the extension.', 403))
    def deleteExtension(self, app_id, item):
        """
        Delete the extension by ID.

        :param app_id: Application ID
        :param ext_id: Extension ID
        :return: Confirmation message with the name of the deleted extension
        """
        Item().remove(item)
        return {'message': 'Deleted extension %s.' % item['name']}