Ejemplo n.º 1
0
def get_neo4j_node(resource_name, uid):
    """
    Get a Neo4j node based on a label and unique identifier.

    :param str resource_name: a neomodel model label
    :param str uid: a string of the unique identifier defined in the neomodel model
    :return: a neomodel model object
    :raises ValidationError: if the requested resource doesn't exist or doesn't have a
    UniqueIdProperty
    """
    # To prevent a ciruclar import, we must import this here
    from estuary.models import all_models

    for model in all_models:
        if model.__label__.lower() == resource_name.lower():
            for _, prop_def in model.__all_properties__:
                if isinstance(prop_def, UniqueIdProperty):
                    return model.nodes.get_or_none(**{prop_def.name: uid})

    # Some models don't have unique ID's and those should be skipped
    models_wo_uid = ('DistGitRepo', 'DistGitBranch')
    model_names = [
        model.__name__.lower() for model in all_models
        if model.__name__ not in models_wo_uid
    ]
    error = (
        'The requested resource "{0}" is invalid. Choose from the following: '
        '{1}, and {2}.'.format(resource_name, ', '.join(model_names[:-1]),
                               model_names[-1]))
    raise ValidationError(error)
Ejemplo n.º 2
0
    def find_or_none(cls, identifier):
        """
        Find the node using the supplied identifier.

        :param str identifier: the identifier to search the node by
        :return: the node or None
        :rtype: EstuaryStructuredNode or None
        """
        uid = identifier
        if re.match(r'^\d+$', uid):
            # The identifier is an ID
            return cls.nodes.get_or_none(id_=uid)
        elif uid.endswith('.src.rpm'):
            # The identifer is likely an NVR with .src.rpm at the end, so strip that part of it
            # so it can be treated like a normal NVR
            uid = uid[:-8]

        if len(uid.rsplit('-', 2)) == 3:
            # The identifier looks like an NVR
            nvr = uid.rsplit('-', 2)
            return cls.nodes.get_or_none(name=nvr[0],
                                         version=nvr[1],
                                         release=nvr[2])

        raise ValidationError(
            '"{0}" is not a valid identifier'.format(identifier))
Ejemplo n.º 3
0
def get_neo4j_node(resource_name, uid):
    """
    Get a Neo4j node based on a label and unique identifier.

    :param str resource_name: a neomodel model label
    :param str uid: a string of the unique identifier defined in the neomodel model
    :return: a neomodel model object
    :raises ValidationError: if the requested resource doesn't exist or doesn't have a
        UniqueIdProperty
    """
    # To prevent a ciruclar import, we must import this here
    from estuary.models import all_models

    for model in all_models:
        if model.__label__.lower() == resource_name.lower():
            try:
                return model.find_or_none(uid)
            except RuntimeError:
                # There is no UniqueIdProperty on this model so raise an exception
                models_wo_uid = ('DistGitRepo', 'DistGitBranch')
                model_names = [
                    model.__name__.lower() for model in all_models
                    if model.__name__ not in models_wo_uid
                ]
                error = (
                    'The requested resource "{0}" is invalid. Choose from the following: '
                    '{1}, and {2}.'.format(resource_name,
                                           ', '.join(model_names[:-1]),
                                           model_names[-1]))
                raise ValidationError(error)
Ejemplo n.º 4
0
def create_story_query(item, uid_name, uid, reverse=False, limit=False):
    """
    Create a raw cypher query for story of an artifact.

    :param node item: a Neo4j node whose story is requested by the user
    :param str uid_name: name of node's UniqueIdProperty
    :param str uid: value of node's UniqueIdProperty
    :param bool reverse: boolean value to specify the direction to proceed
    from current node corresponding to the story_flow
    :return: a string containing raw cypher query to retrieve the story of an artifact from Neo4j
    :rtype: str
    """
    # To avoid circular imports
    from estuary.models import story_flow_list

    query = ''

    if reverse is True:
        rel_label = 'backward_relationship'
        node_label = 'backward_label'
    else:
        rel_label = 'forward_relationship'
        node_label = 'forward_label'

    curr_node_label = item.__label__
    if curr_node_label not in story_flow_list:
        raise ValidationError(
            'The story is not available for this kind of resource')

    while True:
        curr_node_info = story_flow(curr_node_label)
        if not curr_node_info:
            break

        if curr_node_label == item.__label__:
            query = """\
                MATCH ({var}:{label} {{{uid_name}:"{uid}"}})
                CALL apoc.path.expandConfig({var}, {{sequence:\'{label}
                """.format(var=curr_node_label.lower(),
                           label=curr_node_label,
                           uid_name=uid_name.rstrip('_'),
                           uid=uid)

        query += ', {0}, {1}'.format(curr_node_info[rel_label],
                                     curr_node_info[node_label])

        curr_node_label = curr_node_info[node_label]

    if query:
        query += """\
            \', minLevel:1}) YIELD path
            RETURN path
            ORDER BY length(path) DESC
            """

    if query and limit:
        query += ' LIMIT 1'

    return query
Ejemplo n.º 5
0
    def get_story_nodes(self, item, reverse=False, limit=False):
        """
        Create a raw cypher query for story of an artifact and query neo4j with it.

        :param node item: a Neo4j node whose story is requested by the user
        :kwarg bool reverse: specifies the direction to proceed from current node
            corresponding to the story_flow
        :kwarg bool limit: specifies if LIMIT keyword should be added to the created cypher query
        :return: story paths for a particular artifact
        :rtype: list
        """
        query = ''

        if reverse is True:
            rel_label = 'backward_relationship'
            node_label = 'backward_label'
        else:
            rel_label = 'forward_relationship'
            node_label = 'forward_label'

        curr_node_label = item.__label__
        if curr_node_label not in self.story_flow_list:
            raise ValidationError(
                'The story is not available for this kind of resource')

        while True:
            curr_node_info = self.story_flow(curr_node_label)
            if not curr_node_info:
                break

            if curr_node_label == item.__label__:
                query = """\
                    MATCH ({var}:{label}) WHERE id({var})= {node_id}
                    CALL apoc.path.expandConfig({var}, {{sequence:\'{label}
                    """.format(var=curr_node_label.lower(),
                               label=curr_node_label,
                               node_id=item.id)

            query += ', {0}, {1}'.format(curr_node_info[rel_label],
                                         curr_node_info[node_label])

            curr_node_label = curr_node_info[node_label]

        if query:
            query += """\
                \', minLevel:1}) YIELD path
                RETURN path
                ORDER BY length(path) DESC
                """

        if query and limit:
            query += ' LIMIT 1'

        results = []
        if query:
            results, _ = db.cypher_query(query)
        return results
Ejemplo n.º 6
0
    def find_or_none(cls, identifier):
        """
        Find the node using the supplied identifier.

        :param str identifier: the identifier to search the node by
        :return: the node or None
        :rtype: EstuaryStructuredNode or None
        """
        uid = identifier
        if uid.lower().startswith('rhbz'):
            uid = uid[4:]
        if uid.startswith('#'):
            uid = uid[1:]

        # If we are left with something other than digits, then it is an invalid identifier
        if not re.match(r'^\d+$', uid):
            raise ValidationError(
                '"{0}" is not a valid identifier'.format(identifier))

        return cls.nodes.get_or_none(id_=uid)
Ejemplo n.º 7
0
    def find_or_none(cls, identifier):
        """
        Find the node using the supplied identifier.

        :param str identifier: the identifier to search the node by
        :return: the node or None
        :rtype: EstuaryStructuredNode or None
        """
        if re.match(r'^\d+$', identifier):
            # The identifier is an ID
            return cls.nodes.get_or_none(id_=identifier)
        elif re.match(r'^RH[A-Z]{2}-\d{4}:\d+-\d+$', identifier):
            # The identifier is a full advisory name
            return cls.nodes.get_or_none(advisory_name=identifier)
        elif re.match(r'^RH[A-Z]{2}-\d{4}:\d+$', identifier):
            # The identifier is most of the advisory name, so return the latest iteration of this
            # advisory
            return cls.nodes.filter(advisory_name__regex=r'^{0}-\d+$'.format(identifier))\
                .order_by('advisory_name').first_or_none()
        else:
            raise ValidationError('"{0}" is not a valid identifier'.format(identifier))
Ejemplo n.º 8
0
def get_artifact_relationships(resource, uid, relationship):
    """
    Get one-to-many relationships of a particular artifact.

    :param str resource: a resource name that maps to a neomodel class
    :param str uid: the value of the UniqueIdProperty to query with
    :param str relationship: relationship to expand
    :return: a Flask JSON response
    :rtype: flask.Response
    :raises NotFound: if the item is not found
    :raises ValidationError: if an invalid resource/relationship was requested
    """
    item = get_neo4j_node(resource, uid)
    if not item:
        raise NotFound('This item does not exist')

    if relationship not in [rel[0] for rel in item.__all_relationships__]:
        raise ValidationError(
            'Please provide a valid relationship name for {0} with uid {1}'.
            format(resource, uid))

    rel_display_name = relationship.replace('_', ' ')
    results = {
        'data': [],
        'meta': {
            'description': '{0} of {1}'.format(rel_display_name,
                                               item.display_name)
        }
    }

    related_nodes = getattr(item, relationship).match()
    for node in related_nodes:
        serialized_node = node.serialized_all
        serialized_node['resource_type'] = node.__label__
        serialized_node['display_name'] = node.display_name
        results['data'].append(serialized_node)

    return jsonify(results)
Ejemplo n.º 9
0
def get_siblings(resource, uid):
    """
    Get siblings of next/previous node that are correlated to the node in question.

    :param str resource: a resource name that maps to a neomodel class
    :param str uid: the value of the UniqueIdProperty to query with
    :return: a Flask JSON response
    :rtype: flask.Response
    :raises NotFound: if the item is not found
    :raises ValidationError: if an invalid resource was requested
    """
    story_type_mapper = {
        'container': 'ContainerStoryManager',
        'module': 'ModuleStoryManager'
    }
    story_type = request.args.get('story_type', 'container').lower()

    story_manager_class = story_type_mapper.get(story_type)
    if story_manager_class:
        story_manager = getattr(estuary.utils.story, story_manager_class)()
    else:
        raise ValidationError(
            'Supplied story type is invalid. Select from: {0}'.format(
                ', '.join(current_app.config['STORY_MANAGER_SEQUENCE'])))

    # This is the node that is part of a story which has the relationship to the desired
    # sibling nodes
    story_node = get_neo4j_node(resource, uid)
    if not story_node:
        raise NotFound('This item does not exist')

    story_node_story_flow = story_manager.story_flow(story_node.__label__)
    # If backward_rel is true, we fetch siblings of the previous node, next node otherwise.
    # For example, if you pass in an advisory and want to know the Freshmaker events triggered from
    # that advisory, backward_rel would be false. If you want to know the Koji builds attached to
    # that advisory, then backward_rel would true.
    backward = str_to_bool(request.args.get('backward_rel'))
    if backward and story_node_story_flow['backward_label']:
        desired_siblings_label = story_node_story_flow['backward_label']
    elif not backward and story_node_story_flow['forward_label']:
        desired_siblings_label = story_node_story_flow['forward_label']
    else:
        raise ValidationError(
            'Siblings cannot be determined on this kind of resource')

    sibling_nodes = story_manager.get_sibling_nodes(desired_siblings_label,
                                                    story_node)
    # Inflating and formatting results from Neo4j
    serialized_results = []
    for result in sibling_nodes:
        inflated_node = inflate_node(result[0])
        serialized_node = inflated_node.serialized_all
        serialized_node['resource_type'] = inflated_node.__label__
        serialized_node['display_name'] = inflated_node.display_name
        serialized_results.append(serialized_node)

    description = story_manager.get_siblings_description(
        story_node.display_name, story_node_story_flow, backward)

    result = {'data': serialized_results, 'meta': {'description': description}}

    return jsonify(result)