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