Mercurial > farol > farolluz
diff farolluz/producttree.py @ 26:809db989cac5
Reorganize the code in smaller mpodules
author | Benoît Allard <benoit.allard@greenbone.net> |
---|---|
date | Fri, 24 Oct 2014 17:01:26 +0200 |
parents | farolluz/cvrf.py@4004b67216a9 |
children | b87f2a6e613a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/producttree.py Fri Oct 24 17:01:26 2014 +0200 @@ -0,0 +1,365 @@ +# -*- coding: utf-8 -*- +# +# Authors: +# BenoƮt Allard <benoit.allard@greenbone.net> +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +"""\ +Product Tree Objects related to CVRF Documents +""" + +from .common import ValidationError + +class CVRFProductTree(object): + def __init__(self): + # All the branches, they can be order with their `parent` attribute + self._branches = [] + self._groups = [] + self._relationships = [] + self._products = [] + self._groups = [] + + def addProduct(self, product): + """ Add to the product list """ + self._products.append(product) + + def addRelationship(self, rel): + self._relationships.append(rel) + + def addGroup(self, group): + self._groups.append(group) + + def getProductForID(self, productid): + for product in self._products: + if product._productid == productid: + return product + raise KeyError(productid) + + def getGroupForID(self, groupid): + for group in self._groups: + if group._groupid == groupid: + return group + raise KeyError(groupid) + + def decomposeProduct(self, productid): + """ In case of product defined as a relationship (product X installed + on OS Y), this gives us the following tuple: (OS, product). """ + product = self.getProductForID(productid) + parent = product._parent + if parent is None: + return (None, None) + if not isinstance(parent, CVRFRelationship): + return (None, None) + relationtype = parent._relationtype.replace(' ', '').lower() + if relationtype not in ('defaultcomponentof', 'installedon'): + return (None, None) + return ( + self.getProductForID(parent._relatestoproductreference), + self.getProductForID(parent._productreference) + ) + + def getBranch(self, path): + if len(path) == 0: + return self + branches = self._branches + node = None + for idx in path: + node = branches[idx] + branches = node._childs + return node + + def getBranches(self): + for branch in self._branches: + yield branch + for sub_branch in branch.getBranches(): + yield sub_branch + + def getPath(self): + return () + + def getNameOfRelationship(self, relationship): + if relationship is None: + return '' + return ' '.join((self.getProductForID(relationship._productreference)._name, 'as', + relationship._relationtype.lower(), + self.getProductForID(relationship._relatestoproductreference)._name)) + + def getOrphanedBranches(self, product=None): + """ The branches that could accept `product` as Product Definition """ + white_list = [] + if product is not None: + white_list = [product._parent] + for branch in self.getBranches(): + if (branch in white_list) or branch.isOrphaned(): + yield branch + + def getNotTerminalBranches(self, b2=None): + """\ + The branches that could accept `b2` as new sub-branches + Note that b2 and all its sub-branches cannot be listed + """ + black_list = [] + if b2 is not None: + black_list = [b2] + list(b2.getBranches()) + for branch in self.getBranches(): + if branch in black_list: + continue + if branch._product is None: + yield branch + + def getOrphanedRelationships(self, product=None): + """ The relationships that need a product defninition """ + white_list = [] + if product is not None: + white_list = [product.getCurrentRelationship()] + for i, relationship in enumerate(self._relationships): + if (relationship in white_list) or relationship.isOrphaned(): + yield (i, relationship) + + def nbProducts(self): + """ Amount of 'raw' Products """ + return len([p for p in self._products if p._parent is self]) + + def validate(self): + for branch in self._branches: + branch.validate() + productids = set() + for product in self._products: + product.validate() + if product._productid in productids: + raise ValidationError('Each ProductID must be unique (%s)' % product._productid) + productids.add(product._productid) + for relationship in self._relationships: + relationship.validate() + for productid in (relationship._productreference, + relationship._relatestoproductreference): + if productid not in productids: + raise ValidationError('ProductID %s is unknown' % productid) + groupids = set() + for group in self._groups: + group.validate() + if group._groupid in groupids: + raise ValidationError('Duplicated GroupID: %s' % group._groupid) + groupids.add(group._groupid) + for productid in group._productids: + if productid not in productids: + raise ValidationError('ProductID %s is unknown' % productid) + return productids, groupids + + def __str__(self): + return 'Products: %s' % '\n'.join(str(p) for p in self._products) + + +class CVRFProductBranch(object): + TYPES = ('Vendor', 'Product Family', 'Product Name', 'Product Version', + 'Patch Level', 'Service Pack', 'Architecture', 'Language', + 'Legacy', 'Specification') + def __init__(self, _type, name, parentbranch): + self._type = _type + self._name = name + self._childs = [] + self._product = None + self.link(parentbranch) + + def getParent(self): + return self._parentbranch + + def getPath(self, string=False): + """ return the path to that branch element as a tuple """ + if self.isRoot(): + for i, b in enumerate(self._parentbranch._branches): + if b is self: + if string: + return '%d' % i + return (i, ) + else: + for i, b in enumerate(self._parentbranch._childs): + if b is self: + if string: + return '/'.join([self._parentbranch.getPath(string), '%d' % i]) + return self._parentbranch.getPath(string) + (i,) + if string: + return '' + return () + + def getTree(self): + """ this returns a list of tuples (type, name) leading to here""" + if self.isRoot(): + return [(self._type, self._name)] + return self._parentbranch.getTree() + [(self._type, self._name)] + + def getName(self): + return ' / '.join("%s: %s" % (type_, name) for type_, name in self.getTree()) + + def getParentPath(self): + """ return as string the path to the parent """ + return '/'.join('%s' % p for p in self.getPath()[:-1]) + + def isRoot(self): + return isinstance(self._parentbranch, CVRFProductTree) + + def isOrphaned(self): + """ Has no childs and no product """ + return len(self._childs) == 0 and (self._product is None) + + def getBranches(self): + for branch in self._childs: + yield branch + for sub_branch in branch.getBranches(): + yield sub_branch + + def unlink(self): + """ Unset our _parent, and remove us from the _parent._childs """ + if self.isRoot(): + self.getParent()._branches.remove(self) + else: + self.getParent()._childs.remove(self) + self._parentbranch = None + + def link(self, parent): + """ Actually, only set the parent """ + self._parentbranch = parent + if self.isRoot(): + parent._branches.append(self) + else: + parent._childs.append(self) + + + def validate(self): + if not self._type: + raise ValidationError('A Branch must have a Type') + if self._type not in self.TYPES: + raise ValidationError('A Branch Type must be one of %s' % ', '.join(self.TYPES)) + if not self._name: + raise ValidationError('A Branch must have a Name') + for branch in self._childs: + branch.validate() + if self.isOrphaned(): + raise ValidationError('A Branch must have at least a sub-product or sub-branches') + + def __str__(self): + return "%s: %s" % (self._type, self._name) + + +class CVRFFullProductName(object): + def __init__(self, productid, name, parent, cpe=None): + self._productid = productid + self._name = name + self._cpe = cpe + # Can be None (directly under the tree), a ProductBranch, or a + # Relationship + self.link(parent) + + def isRoot(self): + return isinstance(self._parent, CVRFProductTree) + + def isRelationship(self): + return isinstance(self._parent, CVRFRelationship) + + def getTree(self): + if not isinstance(self._parent, CVRFProductBranch): + return [] + return self._parent.getTree() + + def getParentPath(self): + if self.isRoot() or self.isRelationship(): + return '' + return self._parent.getPath(True) + + def getCurrentRelationship(self): + if self.isRelationship(): + return self._parent + return None + + def unlink(self): + """ Unset our _parent, and remove us from the _parent._childs + We are still in the product list. + """ + if not self.isRoot(): + self._parent._product = None + self._parent = None + + def link(self, parent): + self._parent = parent + if not self.isRoot(): + parent._product = self + + def validate(self): + if not self._productid: + raise ValidationError('A Product must have a ProductID') + if not self._name: + raise ValidationError('A Product must have a Name') + + def __str__(self): + return "%s (%s)" % (self._productid, self._name) + + +class CVRFRelationship(object): + TYPES = ('Default Component Of', 'Optional Component Of', + 'External Component Of', 'Installed On', 'Installed With') + def __init__(self, productref, reltype, relatestoproductref): + self._productreference = productref + self._relationtype = reltype + self._relatestoproductreference = relatestoproductref + self._product = None + + def getParent(self): + """ All parent element of a FullProductName should implement that + method """ + return None + + def isOrphaned(self): + return self._product is None + + def validate(self): + if not self._productreference: + raise ValidationError('A Relationship must have a Product Reference') + if not self._relationtype: + raise ValidationError('A Relationship must have a Relation Type') + if self._relationtype not in self.TYPES: + raise ValidationError('Relation Type must be one of %s' % ', '.join(self.TYPES)) + if not self._relatestoproductreference: + raise ValidationError('A Relationship must have a "Relates To product Reference"') + if self._productreference == self._relatestoproductreference: + raise ValidationError('A Relationship cannot reference twice the same Product') + + +class CVRFGroup(object): + def __init__(self, groupid): + self._groupid = groupid + self._description = None + self._productids = [] + + def setDescription(self, description): + self._description = description + + def addProductID(self, productid): + self._productids.append(productid) + + def getTitle(self): + if self._description: + return "%s (%d products)" % (self._description, len(self._productids)) + return "#%s (%d products)" % (self._groupid, len(self._productids)) + + def validate(self): + if not self._groupid: + raise ValidationError('A Group must have a GroupID') + if not self._productids or len(self._productids) < 2: + raise ValidationError('A Group must contain at least two products') +