mirror of
https://github.com/certbot/certbot.git
synced 2026-01-26 07:41:33 +03:00
[Apache v2] Implement find_ancestors (#7561)
* Implement find_ancestors * Create the node properly and add assertions * Update certbot-apache/certbot_apache/augeasparser.py Co-Authored-By: ohemorange <ebportnoy@gmail.com> * Remove comment
This commit is contained in:
@@ -24,6 +24,14 @@ class ApacheParserNode(interfaces.ParserNode):
|
||||
def save(self, msg): # pragma: no cover
|
||||
pass
|
||||
|
||||
def find_ancestors(self, name): # pylint: disable=unused-variable
|
||||
"""Find ancestor BlockNodes with a given name"""
|
||||
return [ApacheBlockNode(name=assertions.PASS,
|
||||
parameters=assertions.PASS,
|
||||
ancestor=self,
|
||||
filepath=assertions.PASS,
|
||||
metadata=self.metadata)]
|
||||
|
||||
|
||||
class ApacheCommentNode(ApacheParserNode):
|
||||
""" apacheconfig implementation of CommentNode interface """
|
||||
|
||||
@@ -75,7 +75,6 @@ from certbot_apache import parser
|
||||
from certbot_apache import parsernode_util as util
|
||||
|
||||
|
||||
|
||||
class AugeasParserNode(interfaces.ParserNode):
|
||||
""" Augeas implementation of ParserNode interface """
|
||||
|
||||
@@ -100,6 +99,63 @@ class AugeasParserNode(interfaces.ParserNode):
|
||||
def save(self, msg):
|
||||
self.parser.save(msg)
|
||||
|
||||
def find_ancestors(self, name):
|
||||
"""
|
||||
Searches for ancestor BlockNodes with a given name.
|
||||
|
||||
:param str name: Name of the BlockNode parent to search for
|
||||
|
||||
:returns: List of matching ancestor nodes.
|
||||
:rtype: list of AugeasBlockNode
|
||||
"""
|
||||
|
||||
ancestors = []
|
||||
|
||||
parent = self.metadata["augeaspath"]
|
||||
while True:
|
||||
# Get the path of ancestor node
|
||||
parent = parent.rpartition("/")[0]
|
||||
if not parent:
|
||||
break
|
||||
anc = self._create_blocknode(parent)
|
||||
if anc.name.lower() == name.lower():
|
||||
ancestors.append(anc)
|
||||
|
||||
return ancestors
|
||||
|
||||
def _create_blocknode(self, path):
|
||||
"""
|
||||
Helper function to create a BlockNode from Augeas path. This is used by
|
||||
AugeasParserNode.find_ancestors and AugeasBlockNode.
|
||||
and AugeasBlockNode.find_blocks
|
||||
|
||||
"""
|
||||
|
||||
name = self._aug_get_name(path)
|
||||
metadata = {"augeasparser": self.parser, "augeaspath": path}
|
||||
|
||||
return AugeasBlockNode(name=name,
|
||||
ancestor=assertions.PASS,
|
||||
filepath=apache_util.get_file_path(path),
|
||||
metadata=metadata)
|
||||
|
||||
def _aug_get_name(self, path):
|
||||
"""
|
||||
Helper function to get name of a configuration block or variable from path.
|
||||
"""
|
||||
|
||||
# Remove the ending slash if any
|
||||
if path[-1] == "/": # pragma: no cover
|
||||
path = path[:-1]
|
||||
|
||||
# Get the block name
|
||||
name = path.split("/")[-1]
|
||||
|
||||
# remove [...], it's not allowed in Apache configuration and is used
|
||||
# for indexing within Augeas
|
||||
name = name.split("[")[0]
|
||||
return name
|
||||
|
||||
|
||||
class AugeasCommentNode(AugeasParserNode):
|
||||
""" Augeas implementation of CommentNode interface """
|
||||
@@ -263,7 +319,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
|
||||
metadata=new_metadata)
|
||||
return new_comment
|
||||
|
||||
def find_blocks(self, name, exclude=True): # pylint: disable=unused-argument
|
||||
def find_blocks(self, name, exclude=True):
|
||||
"""Recursive search of BlockNodes from the sequence of children"""
|
||||
|
||||
nodes = list()
|
||||
@@ -275,7 +331,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
|
||||
|
||||
return nodes
|
||||
|
||||
def find_directives(self, name, exclude=True): # pylint: disable=unused-argument
|
||||
def find_directives(self, name, exclude=True):
|
||||
"""Recursive search of DirectiveNodes from the sequence of children"""
|
||||
|
||||
nodes = list()
|
||||
@@ -353,19 +409,6 @@ class AugeasBlockNode(AugeasDirectiveNode):
|
||||
filepath=apache_util.get_file_path(path),
|
||||
metadata=metadata)
|
||||
|
||||
def _create_blocknode(self, path):
|
||||
"""Helper function to create a BlockNode from Augeas path"""
|
||||
|
||||
name = self._aug_get_name(path)
|
||||
metadata = {"augeasparser": self.parser, "augeaspath": path}
|
||||
|
||||
# Because of the dynamic nature, and the fact that we're not populating
|
||||
# the complete ParserNode tree, we use the search parent as ancestor
|
||||
return AugeasBlockNode(name=name,
|
||||
ancestor=assertions.PASS,
|
||||
filepath=apache_util.get_file_path(path),
|
||||
metadata=metadata)
|
||||
|
||||
def _aug_find_blocks(self, name):
|
||||
"""Helper function to perform a search to Augeas DOM tree to search
|
||||
configuration blocks with a given name"""
|
||||
@@ -380,23 +423,6 @@ class AugeasBlockNode(AugeasDirectiveNode):
|
||||
name.lower() in os.path.basename(path).lower()])
|
||||
return blk_paths
|
||||
|
||||
def _aug_get_name(self, path):
|
||||
"""
|
||||
Helper function to get name of a configuration block or variable from path.
|
||||
"""
|
||||
|
||||
# Remove the ending slash if any
|
||||
if path[-1] == "/": # pragma: no cover
|
||||
path = path[:-1]
|
||||
|
||||
# Get the block name
|
||||
name = path.split("/")[-1]
|
||||
|
||||
# remove [...], it's not allowed in Apache configuration and is used
|
||||
# for indexing within Augeas
|
||||
name = name.split("[")[0]
|
||||
return name
|
||||
|
||||
def _aug_resolve_child_position(self, name, position):
|
||||
"""
|
||||
Helper function that iterates through the immediate children and figures
|
||||
|
||||
@@ -18,10 +18,59 @@ class DualNodeBase(object):
|
||||
""" Attribute value assertion """
|
||||
firstval = getattr(self.primary, aname)
|
||||
secondval = getattr(self.secondary, aname)
|
||||
if not callable(firstval):
|
||||
exclusions = [
|
||||
# Metadata will inherently be different, as ApacheParserNode does
|
||||
# not have Augeas paths and so on.
|
||||
aname == "metadata",
|
||||
callable(firstval)
|
||||
]
|
||||
if not any(exclusions):
|
||||
assertions.assertEqualSimple(firstval, secondval)
|
||||
return firstval
|
||||
|
||||
def find_ancestors(self, name):
|
||||
""" Traverses the ancestor tree and returns ancestors matching name """
|
||||
return self._find_helper(DualBlockNode, "find_ancestors", name)
|
||||
|
||||
def _find_helper(self, nodeclass, findfunc, search, **kwargs):
|
||||
"""A helper for find_* functions. The function specific attributes should
|
||||
be passed as keyword arguments.
|
||||
|
||||
:param interfaces.ParserNode nodeclass: The node class for results.
|
||||
:param str findfunc: Name of the find function to call
|
||||
:param str search: The search term
|
||||
"""
|
||||
|
||||
primary_res = getattr(self.primary, findfunc)(search, **kwargs)
|
||||
secondary_res = getattr(self.secondary, findfunc)(search, **kwargs)
|
||||
|
||||
# The order of search results for Augeas implementation cannot be
|
||||
# assured.
|
||||
|
||||
pass_primary = assertions.isPassNodeList(primary_res)
|
||||
pass_secondary = assertions.isPassNodeList(secondary_res)
|
||||
new_nodes = list()
|
||||
|
||||
if pass_primary and pass_secondary:
|
||||
# Both unimplemented
|
||||
new_nodes.append(nodeclass(primary=primary_res[0],
|
||||
secondary=secondary_res[0])) # pragma: no cover
|
||||
elif pass_primary:
|
||||
for c in secondary_res:
|
||||
new_nodes.append(nodeclass(primary=primary_res[0],
|
||||
secondary=c))
|
||||
elif pass_secondary:
|
||||
for c in primary_res:
|
||||
new_nodes.append(nodeclass(primary=c,
|
||||
secondary=secondary_res[0]))
|
||||
else:
|
||||
assert len(primary_res) == len(secondary_res)
|
||||
matches = self._create_matching_list(primary_res, secondary_res)
|
||||
for p, s in matches:
|
||||
new_nodes.append(nodeclass(primary=p, secondary=s))
|
||||
|
||||
return new_nodes
|
||||
|
||||
|
||||
class DualCommentNode(DualNodeBase):
|
||||
""" Dual parser implementation of CommentNode interface """
|
||||
@@ -223,44 +272,6 @@ class DualBlockNode(DualNodeBase):
|
||||
|
||||
return self._find_helper(DualCommentNode, "find_comments", comment)
|
||||
|
||||
def _find_helper(self, nodeclass, findfunc, search, **kwargs):
|
||||
"""A helper for find_* functions. The function specific attributes should
|
||||
be passed as keyword arguments.
|
||||
|
||||
:param interfaces.ParserNode nodeclass: The node class for results.
|
||||
:param str findfunc: Name of the find function to call
|
||||
:param str search: The search term
|
||||
"""
|
||||
|
||||
primary_res = getattr(self.primary, findfunc)(search, **kwargs)
|
||||
secondary_res = getattr(self.secondary, findfunc)(search, **kwargs)
|
||||
|
||||
# The order of search results for Augeas implementation cannot be
|
||||
# assured.
|
||||
|
||||
pass_primary = assertions.isPassNodeList(primary_res)
|
||||
pass_secondary = assertions.isPassNodeList(secondary_res)
|
||||
new_nodes = list()
|
||||
|
||||
if pass_primary and pass_secondary:
|
||||
# Both unimplemented
|
||||
new_nodes.append(nodeclass(primary=primary_res[0],
|
||||
secondary=secondary_res[0])) # pragma: no cover
|
||||
elif pass_primary:
|
||||
for c in secondary_res:
|
||||
new_nodes.append(nodeclass(primary=primary_res[0],
|
||||
secondary=c))
|
||||
elif pass_secondary:
|
||||
for c in primary_res:
|
||||
new_nodes.append(nodeclass(primary=c,
|
||||
secondary=secondary_res[0]))
|
||||
else:
|
||||
assert len(primary_res) == len(secondary_res)
|
||||
matches = self._create_matching_list(primary_res, secondary_res)
|
||||
for p, s in matches:
|
||||
new_nodes.append(nodeclass(primary=p, secondary=s))
|
||||
|
||||
return new_nodes
|
||||
|
||||
def delete_child(self, child):
|
||||
"""Deletes a child from the ParserNode implementations. The actual
|
||||
|
||||
@@ -192,6 +192,18 @@ class ParserNode(object):
|
||||
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def find_ancestors(self, name):
|
||||
"""
|
||||
Traverses the ancestor tree up, searching for BlockNodes with a specific
|
||||
name.
|
||||
|
||||
:param str name: Name of the ancestor BlockNode to search for
|
||||
|
||||
:returns: A list of ancestor BlockNodes that match the name
|
||||
:rtype: list of BlockNode
|
||||
"""
|
||||
|
||||
|
||||
# Linter rule exclusion done because of https://github.com/PyCQA/pylint/issues/179
|
||||
@six.add_metaclass(abc.ABCMeta) # pylint: disable=abstract-method
|
||||
|
||||
@@ -291,3 +291,24 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
|
||||
self.config.parser_root.add_child_directive,
|
||||
"ThisRaisesErrorBecauseMissingParameters"
|
||||
)
|
||||
|
||||
def test_find_ancestors(self):
|
||||
vhsblocks = self.config.parser_root.find_blocks("VirtualHost")
|
||||
macro_test = False
|
||||
nonmacro_test = False
|
||||
for vh in vhsblocks:
|
||||
if "/macro/" in vh.metadata["augeaspath"].lower():
|
||||
ancs = vh.find_ancestors("Macro")
|
||||
self.assertEqual(len(ancs), 1)
|
||||
macro_test = True
|
||||
else:
|
||||
ancs = vh.find_ancestors("Macro")
|
||||
self.assertEqual(len(ancs), 0)
|
||||
nonmacro_test = True
|
||||
self.assertTrue(macro_test)
|
||||
self.assertTrue(nonmacro_test)
|
||||
|
||||
def test_find_ancestors_bad_path(self):
|
||||
self.config.parser_root.primary.metadata["augeaspath"] = ""
|
||||
ancs = self.config.parser_root.primary.find_ancestors("Anything")
|
||||
self.assertEqual(len(ancs), 0)
|
||||
|
||||
@@ -412,3 +412,12 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
|
||||
self.assertFalse(self.block == ne_block)
|
||||
self.assertFalse(self.directive == ne_directive)
|
||||
self.assertFalse(self.comment == ne_comment)
|
||||
|
||||
def test_find_ancestors(self):
|
||||
primarymock = mock.MagicMock(return_value=[])
|
||||
secondarymock = mock.MagicMock(return_value=[])
|
||||
self.block.primary.find_ancestors = primarymock
|
||||
self.block.secondary.find_ancestors = secondarymock
|
||||
self.block.find_ancestors("anything")
|
||||
self.assertTrue(primarymock.called)
|
||||
self.assertTrue(secondarymock.called)
|
||||
|
||||
@@ -24,6 +24,10 @@ class DummyParserNode(interfaces.ParserNode):
|
||||
"""Save"""
|
||||
pass
|
||||
|
||||
def find_ancestors(self, name): # pragma: no cover
|
||||
""" Find ancestors """
|
||||
return []
|
||||
|
||||
|
||||
class DummyCommentNode(DummyParserNode):
|
||||
""" A dummy class implementing CommentNode interface """
|
||||
|
||||
Reference in New Issue
Block a user