From d1de5a9d5ec3e1d3e60df5705791877339c8aca9 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Thu, 14 Nov 2019 11:38:57 +0200 Subject: [PATCH] Implement find_ancestors --- certbot-apache/certbot_apache/augeasparser.py | 94 ++++++++++++------- certbot-apache/certbot_apache/dualparser.py | 8 ++ certbot-apache/certbot_apache/interfaces.py | 12 +++ .../certbot_apache/tests/augeasnode_test.py | 21 +++++ .../certbot_apache/tests/dualnode_test.py | 9 ++ .../certbot_apache/tests/parsernode_test.py | 4 + 6 files changed, 115 insertions(+), 33 deletions(-) diff --git a/certbot-apache/certbot_apache/augeasparser.py b/certbot-apache/certbot_apache/augeasparser.py index 47b8602ab..91ca6d466 100644 --- a/certbot-apache/certbot_apache/augeasparser.py +++ b/certbot-apache/certbot_apache/augeasparser.py @@ -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,65 @@ class AugeasParserNode(interfaces.ParserNode): def save(self, msg): self.parser.save(msg) + def find_ancestors(self, name): + """ + Searches 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} + + # 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_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 """ @@ -253,7 +311,7 @@ class AugeasBlockNode(AugeasDirectiveNode): self.children += (new_comment,) 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() @@ -265,7 +323,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() @@ -343,19 +401,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""" @@ -370,23 +415,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 diff --git a/certbot-apache/certbot_apache/dualparser.py b/certbot-apache/certbot_apache/dualparser.py index 8897449b6..769933ae0 100644 --- a/certbot-apache/certbot_apache/dualparser.py +++ b/certbot-apache/certbot_apache/dualparser.py @@ -21,6 +21,14 @@ class DualNodeBase(object): assertions.assertEqualSimple(firstval, secondval) return firstval + def find_ancestors(self, name): + """ Traverses the ancestor tree and returns ancestors matching name """ + + primary_ancs = self.primary.find_ancestors(name) + secondary_ancs = self.secondary.find_ancestors(name) + assert primary_ancs == secondary_ancs + return primary_ancs + class DualCommentNode(DualNodeBase): """ Dual parser implementation of CommentNode interface """ diff --git a/certbot-apache/certbot_apache/interfaces.py b/certbot-apache/certbot_apache/interfaces.py index ecad2d4eb..2d25fad0f 100644 --- a/certbot-apache/certbot_apache/interfaces.py +++ b/certbot-apache/certbot_apache/interfaces.py @@ -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 diff --git a/certbot-apache/certbot_apache/tests/augeasnode_test.py b/certbot-apache/certbot_apache/tests/augeasnode_test.py index a86b618b2..5012eb133 100644 --- a/certbot-apache/certbot_apache/tests/augeasnode_test.py +++ b/certbot-apache/certbot_apache/tests/augeasnode_test.py @@ -281,3 +281,24 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public- self.config.parser_root.primary.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) diff --git a/certbot-apache/certbot_apache/tests/dualnode_test.py b/certbot-apache/certbot_apache/tests/dualnode_test.py index dbce18431..f0fa818a7 100644 --- a/certbot-apache/certbot_apache/tests/dualnode_test.py +++ b/certbot-apache/certbot_apache/tests/dualnode_test.py @@ -415,3 +415,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) diff --git a/certbot-apache/certbot_apache/tests/parsernode_test.py b/certbot-apache/certbot_apache/tests/parsernode_test.py index 1a2288c82..a6caf4814 100644 --- a/certbot-apache/certbot_apache/tests/parsernode_test.py +++ b/certbot-apache/certbot_apache/tests/parsernode_test.py @@ -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 """