Compare commits
19 Commits
test-pytho
...
parser_ref
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff0d851a1e | ||
|
|
5cfdfc80bd | ||
|
|
f4c962073e | ||
|
|
973c521c3e | ||
|
|
526997107b | ||
|
|
c1362b7437 | ||
|
|
371ece9ea6 | ||
|
|
dd762f3c79 | ||
|
|
8cfd228eee | ||
|
|
0aef744f29 | ||
|
|
4b77350b0a | ||
|
|
6b5c4c9f79 | ||
|
|
267a281a10 | ||
|
|
fbd7d20d47 | ||
|
|
41f24c73f5 | ||
|
|
f74b8ad2ad | ||
|
|
d8806a4c05 | ||
|
|
fd3644a4d7 | ||
|
|
0b545ab443 |
87
certbot-apache/certbot_apache/augeasparser.py
Normal file
87
certbot-apache/certbot_apache/augeasparser.py
Normal file
@@ -0,0 +1,87 @@
|
||||
""" Tests for ParserNode interface """
|
||||
from certbot_apache import interfaces
|
||||
|
||||
|
||||
class AugeasCommentNode(interfaces.CommentNode):
|
||||
""" Augeas implementation of CommentNode interface """
|
||||
ancestor = None
|
||||
comment = ""
|
||||
dirty = False
|
||||
|
||||
def __init__(self, comment, ancestor=None):
|
||||
self.comment = comment
|
||||
self.ancestor = ancestor
|
||||
|
||||
def save(self, msg): # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
class AugeasDirectiveNode(interfaces.DirectiveNode):
|
||||
""" Augeas implementation of DirectiveNode interface """
|
||||
ancestor = None
|
||||
parameters = tuple() # type: Tuple[str, ...]
|
||||
dirty = False
|
||||
enabled = True
|
||||
name = ""
|
||||
|
||||
def __init__(self, name, parameters=tuple(), ancestor=None):
|
||||
self.name = name
|
||||
self.parameters = parameters
|
||||
self.ancestor = ancestor
|
||||
|
||||
def save(self, msg): # pragma: no cover
|
||||
pass
|
||||
|
||||
def set_parameters(self, parameters): # pragma: no cover
|
||||
self.parameters = tuple("CERTBOT_PASS_ASSERT")
|
||||
|
||||
|
||||
class AugeasBlockNode(interfaces.BlockNode):
|
||||
""" Augeas implementation of BlockNode interface """
|
||||
ancestor = None
|
||||
parameters = tuple() # type: Tuple[str, ...]
|
||||
children = tuple() # type: Tuple[interfaces.ParserNode, ...]
|
||||
dirty = False
|
||||
enabled = True
|
||||
name = ""
|
||||
|
||||
def __init__(self, name, parameters=tuple(), ancestor=None):
|
||||
self.name = name
|
||||
self.parameters = parameters
|
||||
self.ancestor = ancestor
|
||||
|
||||
def save(self, msg): # pragma: no cover
|
||||
pass
|
||||
|
||||
def add_child_block(self, name, parameters=None, position=None): # pragma: no cover
|
||||
new_block = AugeasBlockNode("CERTBOT_PASS_ASSERT", ancestor=self)
|
||||
self.children += (new_block,)
|
||||
return new_block
|
||||
|
||||
def add_child_directive(self, name, parameters=None, position=None): # pragma: no cover
|
||||
new_dir = AugeasDirectiveNode("CERTBOT_PASS_ASSERT", ancestor=self)
|
||||
self.children += (new_dir,)
|
||||
return new_dir
|
||||
|
||||
def add_child_comment(self, comment="", position=None): # pragma: no cover
|
||||
new_comment = AugeasCommentNode("CERTBOT_PASS_ASSERT", ancestor=self)
|
||||
self.children += (new_comment,)
|
||||
return new_comment
|
||||
|
||||
def find_blocks(self, name, exclude=True): # pragma: no cover
|
||||
return [AugeasBlockNode("CERTBOT_PASS_ASSERT", ancestor=self)]
|
||||
|
||||
def find_directives(self, name, exclude=True): # pragma: no cover
|
||||
return [AugeasDirectiveNode("CERTBOT_PASS_ASSERT", ancestor=self)]
|
||||
|
||||
def find_comments(self, comment, exact=False): # pragma: no cover
|
||||
return [AugeasCommentNode("CERTBOT_PASS_ASSERT", ancestor=self)]
|
||||
|
||||
def delete_child(self, child): # pragma: no cover
|
||||
pass
|
||||
|
||||
def set_parameters(self, parameters): # pragma: no cover
|
||||
self.parameters = tuple("CERTBOT_PASS_ASSERT")
|
||||
|
||||
def unsaved_files(self): # pragma: no cover
|
||||
return ["CERTBOT_PASS_ASSERT"]
|
||||
@@ -29,6 +29,7 @@ from certbot.plugins.util import path_surgery
|
||||
from certbot.plugins.enhancements import AutoHSTSEnhancement
|
||||
|
||||
from certbot_apache import apache_util
|
||||
from certbot_apache import augeasparser
|
||||
from certbot_apache import constants
|
||||
from certbot_apache import display_ops
|
||||
from certbot_apache import http_01
|
||||
@@ -280,6 +281,10 @@ class ApacheConfigurator(common.Installer):
|
||||
"Unable to create a lock file in {0}. Are you running"
|
||||
" Certbot with sufficient privileges to modify your"
|
||||
" Apache configuration?".format(self.option("server_root")))
|
||||
|
||||
# TODO: apache-parser-v2
|
||||
self.parser_root = augeasparser.AugeasBlockNode(None, ancestor=None)
|
||||
|
||||
self._prepared = True
|
||||
|
||||
def save(self, title=None, temporary=False):
|
||||
@@ -507,10 +512,25 @@ class ApacheConfigurator(common.Installer):
|
||||
"cert_key": self.parser.find_dir("SSLCertificateKeyFile",
|
||||
None, vhost.path)}
|
||||
|
||||
# TODO: apache-parser-v2
|
||||
# We need to add a BlockNode reference to obj.VirtualHost
|
||||
# v2_path = {"cert_path": vhost.node.find_directives(
|
||||
# "SSLCertificateFile"),
|
||||
# "cert_key": vhost.node.find_directives(
|
||||
# "SSLCertificateKeyFile")}
|
||||
# parserassert.legacy_assert_dir(path["cert_path"], v2_path["cert_path"])
|
||||
# parserassert.legacy_assert_dir(path["cert_key"], v2_path["cert_key"])
|
||||
|
||||
# Only include if a certificate chain is specified
|
||||
if chain_path is not None:
|
||||
path["chain_path"] = self.parser.find_dir(
|
||||
"SSLCertificateChainFile", None, vhost.path)
|
||||
# TODO: apache-parser-v2
|
||||
# v2_path["chain_path"] = vhost.node.find_directives(
|
||||
# "SSLCertificateChainFile")
|
||||
# parserassert.legacy_assert_dir(path["chain_path"],
|
||||
# v2_path["chain_path])
|
||||
|
||||
|
||||
# Handle errors when certificate/key directives cannot be found
|
||||
if not path["cert_path"]:
|
||||
@@ -535,9 +555,15 @@ class ApacheConfigurator(common.Installer):
|
||||
set_cert_path = cert_path
|
||||
self.parser.aug.set(path["cert_path"][-1], cert_path)
|
||||
self.parser.aug.set(path["cert_key"][-1], key_path)
|
||||
# TODO: apache-parser-v2
|
||||
# path["cert_path"][-1].set_parameters(cert_path,)
|
||||
# path["cert_key"][-1].set_parameters(key_path,)
|
||||
if chain_path is not None:
|
||||
self.parser.add_dir(vhost.path,
|
||||
"SSLCertificateChainFile", chain_path)
|
||||
# TODO: apache-parser-v2
|
||||
# vhost.node.add_child_directive("SSLCertificateChainFile",
|
||||
# (chain_path,))
|
||||
else:
|
||||
raise errors.PluginError("--chain-path is required for your "
|
||||
"version of Apache")
|
||||
@@ -548,6 +574,9 @@ class ApacheConfigurator(common.Installer):
|
||||
set_cert_path = fullchain_path
|
||||
self.parser.aug.set(path["cert_path"][-1], fullchain_path)
|
||||
self.parser.aug.set(path["cert_key"][-1], key_path)
|
||||
# TODO: apache-parser-v2
|
||||
# path["cert_path"][-1].set_parameters(fullchain_path,)
|
||||
# path["cert_key"][-1].set_parameters(key_path,)
|
||||
|
||||
# Enable the new vhost if needed
|
||||
if not vhost.enabled:
|
||||
@@ -793,29 +822,48 @@ class ApacheConfigurator(common.Installer):
|
||||
|
||||
return ""
|
||||
|
||||
def _get_vhost_names(self, path):
|
||||
def _get_vhost_names(self, vhost):
|
||||
"""Helper method for getting the ServerName and
|
||||
ServerAlias values from vhost in path
|
||||
ServerAlias values from vhost
|
||||
|
||||
:param path: Path to read ServerName and ServerAliases from
|
||||
:param vhost: VirtualHost object to read ServerName and ServerAliases from
|
||||
|
||||
:returns: Tuple including ServerName and `list` of ServerAlias strings
|
||||
"""
|
||||
|
||||
servername_match = self.parser.find_dir(
|
||||
"ServerName", None, start=path, exclude=False)
|
||||
"ServerName", None, start=vhost.path, exclude=False)
|
||||
serveralias_match = self.parser.find_dir(
|
||||
"ServerAlias", None, start=path, exclude=False)
|
||||
"ServerAlias", None, start=vhost.path, exclude=False)
|
||||
# TODO: apache-parser-v2
|
||||
# v2_servername_match = vhost.node.find_directives("ServerName",
|
||||
# exclude=False)
|
||||
# v2_serveralias_match = vhost.node.find_directives("ServerAlias",
|
||||
# exclude=False)
|
||||
# parserassert.legacy_assert_dir(servername_match, v2_servername_match)
|
||||
# parserassert.legacy_assert_dir(serveralias_match, v2_serveralias_match)
|
||||
|
||||
|
||||
serveraliases = []
|
||||
for alias in serveralias_match:
|
||||
serveralias = self.parser.get_arg(alias)
|
||||
serveraliases.append(serveralias)
|
||||
# TODO: apache-parser-v2
|
||||
# v2_serveraliases = []
|
||||
# for alias in v2_serveralias_match:
|
||||
# v2_serveraliases += list(alias.parameters)
|
||||
# parserassert.legacy_assert_list(serveraliases, v2_serveraliases)
|
||||
|
||||
|
||||
servername = None
|
||||
if servername_match:
|
||||
# Get last ServerName as each overwrites the previous
|
||||
servername = self.parser.get_arg(servername_match[-1])
|
||||
# TODO: apache-parser-v2
|
||||
# v2_servername = None
|
||||
# if v2_servername_match:
|
||||
# v2_servername = v2_servername_match[-1].parameters[-1]
|
||||
# parserassert.simpleassert(servername, v2_servername)
|
||||
|
||||
return (servername, serveraliases)
|
||||
|
||||
@@ -827,7 +875,7 @@ class ApacheConfigurator(common.Installer):
|
||||
|
||||
"""
|
||||
|
||||
servername, serveraliases = self._get_vhost_names(host.path)
|
||||
servername, serveraliases = self._get_vhost_names(host)
|
||||
|
||||
for alias in serveraliases:
|
||||
if not host.modmacro:
|
||||
@@ -1476,7 +1524,7 @@ class ApacheConfigurator(common.Installer):
|
||||
|
||||
def _add_servername_alias(self, target_name, vhost):
|
||||
vh_path = vhost.path
|
||||
sname, saliases = self._get_vhost_names(vh_path)
|
||||
sname, saliases = self._get_vhost_names(vhost)
|
||||
if target_name == sname or target_name in saliases:
|
||||
return
|
||||
if self._has_matching_wildcard(vh_path, target_name):
|
||||
|
||||
375
certbot-apache/certbot_apache/interfaces.py
Normal file
375
certbot-apache/certbot_apache/interfaces.py
Normal file
@@ -0,0 +1,375 @@
|
||||
"""Parser interfaces."""
|
||||
import abc
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ParserNode(object):
|
||||
"""
|
||||
ParserNode is the basic building block of the tree of such nodes,
|
||||
representing the structure of the configuration. It is largely meant to keep
|
||||
the structure information intact and idiomatically accessible.
|
||||
|
||||
The root node as well as the child nodes of it should be instances of ParserNode.
|
||||
Nodes keep track of their differences to on-disk representation of configuration
|
||||
by marking modified ParserNodes as dirty to enable partial write-to-disk for
|
||||
different files in the configuration structure.
|
||||
|
||||
While for the most parts the usage and the child types are obvious, "include"-
|
||||
and similar directives are an exception to this rule. This is because of the
|
||||
nature of include directives - which unroll the contents of another file or
|
||||
configuration block to their place. While we could unroll the included nodes
|
||||
to the parent tree, it remains important to keep the context of include nodes
|
||||
separate in order to write back the original configuration as it was.
|
||||
|
||||
For parsers that require the implementation to keep track of the whitespacing,
|
||||
it's responsibility of each ParserNode object itself to store its prepending
|
||||
whitespaces in order to be able to reconstruct the complete configuration file i
|
||||
as it was when originally read from the disk.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def ancestor(self): # pragma: no cover
|
||||
"""
|
||||
This property contains a reference to ancestor node, or None if the node
|
||||
is the root node of the configuration tree.
|
||||
|
||||
:returns: The ancestor BlockNode object, or None for root node.
|
||||
:rtype: ParserNode
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def dirty(self): # pragma: no cover
|
||||
"""
|
||||
This property contains a boolean value of the information if this node has
|
||||
been modified since last save (or after the initial parse).
|
||||
|
||||
:returns: True if this node has had changes that have not yet been written
|
||||
to disk.
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def save(self, msg):
|
||||
"""
|
||||
Save traverses the children, and attempts to write the AST to disk for
|
||||
all the objects that are marked dirty. The actual operation of course
|
||||
depends on the underlying implementation. save() shouldn't be called
|
||||
from the Configurator outside of its designated save() method in order
|
||||
to ensure that the Reverter checkpoints are created properly.
|
||||
|
||||
Note: this approach of keeping internal structure of the configuration
|
||||
within the ParserNode tree does not represent the file inclusion structure
|
||||
of actual configuration files that reside in the filesystem. To handle
|
||||
file writes properly, the file specific temporary trees should be extracted
|
||||
from the full ParserNode tree where necessary when writing to disk.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class CommentNode(ParserNode):
|
||||
"""
|
||||
CommentNode class is used for representation of comments within the parsed
|
||||
configuration structure. Because of the nature of comments, it is not able
|
||||
to have child nodes and hence it is always treated as a leaf node.
|
||||
|
||||
CommentNode stores its contents in class variable 'comment' and does not
|
||||
have a specific name.
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def comment(self): # pragma: no cover
|
||||
"""
|
||||
Comment property contains the contents of the comment without the comment
|
||||
directives (typically # or /* ... */).
|
||||
|
||||
:returns: A string containing the comment
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class DirectiveNode(ParserNode):
|
||||
"""
|
||||
DirectiveNode class represents a configuration directive within the configuration.
|
||||
It can have zero or more arguments attached to it. Because of the nature of
|
||||
single directives, it is not able to have child nodes and hence it is always
|
||||
treated as a leaf node.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def enabled(self): # pragma: no cover
|
||||
"""
|
||||
Configuration blocks may have conditional statements enabling or disabling
|
||||
their contents. This property returns the state of this DirectiveNode.
|
||||
|
||||
:returns: True if the DirectiveNode is parsed and enabled in the configuration.
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def name(self): # pragma: no cover
|
||||
"""
|
||||
Name property contains the name of the directive.
|
||||
|
||||
:returns: Name of this node
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def parameters(self): # pragma: no cover
|
||||
"""
|
||||
This property contains a tuple of parameters of this ParserNode object
|
||||
excluding whitespaces.
|
||||
|
||||
:returns: A tuple of parameters for this node
|
||||
:rtype: tuple
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_parameters(self, parameters):
|
||||
"""
|
||||
Sets the sequence of parameters for this ParserNode object without
|
||||
whitespaces, and marks this object dirty.
|
||||
|
||||
:param list parameters: sequence of parameters
|
||||
"""
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BlockNode(ParserNode):
|
||||
"""
|
||||
BlockNode class represents a block of nested configuration directives, comments
|
||||
and other blocks as its children. A BlockNode can have zero or more arguments
|
||||
attached to it.
|
||||
|
||||
Configuration blocks typically consist of one or more child nodes of all possible
|
||||
types. Because of this, the BlockNode class has various discovery and structure
|
||||
management methods.
|
||||
|
||||
Lists of arguments used as an optional argument for some of the methods should
|
||||
be lists of strings that are applicable arguments for each specific BlockNode
|
||||
or DirectiveNode types. As an example, for a following configuration example:
|
||||
|
||||
<VirtualHost *:80>
|
||||
...
|
||||
</VirtualHost>
|
||||
|
||||
The node type would be BlockNode, name would be 'VirtualHost' and arguments
|
||||
would be: ['*:80'].
|
||||
|
||||
While for the following example:
|
||||
|
||||
LoadModule alias_module /usr/lib/apache2/modules/mod_alias.so
|
||||
|
||||
The node type would be DirectiveNode, name would be 'LoadModule' and arguments
|
||||
would be: ['alias_module', '/usr/lib/apache2/modules/mod_alias.so']
|
||||
|
||||
The applicable arguments are dependent on the underlying configuration language
|
||||
and its grammar.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def add_child_block(self, name, arguments=None, position=None):
|
||||
"""
|
||||
Adds a new BlockNode child node with provided values and marks the callee
|
||||
BlockNode dirty. This is used to add new children to the AST.
|
||||
|
||||
:param str name: The name of the child node to add
|
||||
:param list arguments: list of arguments for the node
|
||||
:param int position: Position in the list of children to add the new child
|
||||
node to. Defaults to None, which appends the newly created node to the list.
|
||||
If an integer is given, the child is inserted before that index in the
|
||||
list similar to list().insert.
|
||||
|
||||
:returns: BlockNode instance of the created child block
|
||||
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def add_child_directive(self, name, arguments=None, position=None):
|
||||
"""
|
||||
Adds a new DirectiveNode child node with provided values and marks the
|
||||
callee BlockNode dirty. This is used to add new children to the AST.
|
||||
|
||||
:param str name: The name of the child node to add
|
||||
:param list arguments: list of arguments for the node
|
||||
:param int position: Position in the list of children to add the new child
|
||||
node to. Defaults to None, which appends the newly created node to the list.
|
||||
If an integer is given, the child is inserted before that index in the
|
||||
list similar to list().insert.
|
||||
|
||||
:returns: DirectiveNode instance of the created child directive
|
||||
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def add_child_comment(self, comment="", position=None):
|
||||
"""
|
||||
Adds a new CommentNode child node with provided value and marks the
|
||||
callee BlockNode dirty. This is used to add new children to the AST.
|
||||
|
||||
:param str comment: Comment contents
|
||||
:param int position: Position in the list of children to add the new child
|
||||
node to. Defaults to None, which appends the newly created node to the list.
|
||||
If an integer is given, the child is inserted before that index in the
|
||||
list similar to list().insert.
|
||||
|
||||
:returns: CommentNode instance of the created child comment
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def children(self): # pragma: no cover
|
||||
"""
|
||||
This property contains a list ParserNode objects that are the children
|
||||
for this node. The order of children is the same as that of the parsed
|
||||
configuration block.
|
||||
|
||||
:returns: A tuple of this block's children
|
||||
:rtype: tuple
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def enabled(self):
|
||||
"""
|
||||
Configuration blocks may have conditional statements enabling or disabling
|
||||
their contents. This property returns the state of this configuration block.
|
||||
In case of unmatched conditional statement in block, this block itself should
|
||||
be set enabled while its children should be set disabled.
|
||||
|
||||
:returns: True if the BlockNode is parsed and enabled in the configuration.
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def find_blocks(self, name, exclude=True):
|
||||
"""
|
||||
Find a configuration block by name. This method walks the child tree of
|
||||
ParserNodes under the instance it was called from. This way it is possible
|
||||
to search for the whole configuration tree, when starting from root node or
|
||||
to do a partial search when starting from a specified branch.
|
||||
|
||||
:param str name: The name of the directive to search for
|
||||
:param bool exclude: If the search results should exclude the contents of
|
||||
ParserNode objects that reside within conditional blocks and because
|
||||
of current state are not enabled.
|
||||
|
||||
:returns: A list of found BlockNode objects.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def find_directives(self, name, exclude=True):
|
||||
"""
|
||||
Find a directive by name. This method walks the child tree of ParserNodes
|
||||
under the instance it was called from. This way it is possible to search
|
||||
for the whole configuration tree, when starting from root node, or to do
|
||||
a partial search when starting from a specified branch.
|
||||
|
||||
:param str name: The name of the directive to search for
|
||||
:param bool exclude: If the search results should exclude the contents of
|
||||
ParserNode objects that reside within conditional blocks and because
|
||||
of current state are not enabled.
|
||||
|
||||
:returns: A list of found DirectiveNode objects.
|
||||
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def find_comments(self, comment, exact=False):
|
||||
"""
|
||||
Find comments with value containing or being exactly the same as search term.
|
||||
|
||||
This method walks the child tree of ParserNodes under the instance it was
|
||||
called from. This way it is possible to search for the whole configuration
|
||||
tree, when starting from root node, or to do a partial search when starting
|
||||
from a specified branch.
|
||||
|
||||
:param str comment: The content of comment to search for
|
||||
:param bool exact: If the comment needs to exactly match the search term
|
||||
|
||||
:returns: A list of found CommentNode objects.
|
||||
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_child(self, child):
|
||||
"""
|
||||
Remove a specified child node from the list of children of the called
|
||||
BlockNode object.
|
||||
|
||||
:param ParserNode child: Child ParserNode object to remove from the list
|
||||
of children of the callee.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def name(self): # pragma: no cover
|
||||
"""
|
||||
Name property contains the name of the block. As an example for config:
|
||||
<VirtualHost *:80> ... </VirtualHost>
|
||||
the name would be "VirtualHost".
|
||||
|
||||
:returns: Name of this node
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def parameters(self): # pragma: no cover
|
||||
"""
|
||||
This property contains a tuple of parameters of this ParserNode object
|
||||
excluding whitespaces.
|
||||
|
||||
:returns: A tuple of parameters for this node
|
||||
:rtype: tuple
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_parameters(self, parameters):
|
||||
"""
|
||||
Sets the sequence of parameters for this ParserNode object without
|
||||
whitespaces, and marks this object dirty.
|
||||
|
||||
:param list parameters: sequence of parameters
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def unsaved_files(self):
|
||||
"""
|
||||
Returns a list of file paths that have been changed since the last save
|
||||
(or the initial configuration parse). The intended use for this method
|
||||
is to tell the Reverter which files need to be included in a checkpoint.
|
||||
|
||||
This is typically called for the root of the ParserNode tree.
|
||||
|
||||
:returns: list of file paths of files that have been changed but not yet
|
||||
saved to disk.
|
||||
"""
|
||||
@@ -61,6 +61,7 @@ class ApacheParser(object):
|
||||
self.root = os.path.abspath(root)
|
||||
self.loc = {"root": self._find_config_root()}
|
||||
self.parse_file(self.loc["root"])
|
||||
self.handle_includes()
|
||||
|
||||
if version >= (2, 4):
|
||||
# Look up variables from httpd and add to DOM if not already parsed
|
||||
@@ -269,6 +270,7 @@ class ApacheParser(object):
|
||||
|
||||
"""
|
||||
mods = set() # type: Set[str]
|
||||
self.handle_includes()
|
||||
matches = self.find_dir("LoadModule")
|
||||
iterator = iter(matches)
|
||||
# Make sure prev_size != cur_size for do: while: iteration
|
||||
@@ -321,11 +323,7 @@ class ApacheParser(object):
|
||||
def update_includes(self):
|
||||
"""Get includes from httpd process, and add them to DOM if needed"""
|
||||
|
||||
# Find_dir iterates over configuration for Include and IncludeOptional
|
||||
# directives to make sure we see the full include tree present in the
|
||||
# configuration files
|
||||
_ = self.find_dir("Include")
|
||||
|
||||
self.handle_includes()
|
||||
inc_cmd = [self.configurator.option("ctl"), "-t", "-D",
|
||||
"DUMP_INCLUDES"]
|
||||
matches = self.parse_from_subprocess(inc_cmd, r"\(.*\) (.*)")
|
||||
@@ -606,6 +604,8 @@ class ApacheParser(object):
|
||||
# includes = self.aug.match(start +
|
||||
# "//* [self::directive='Include']/* [label()='arg']")
|
||||
|
||||
regex = "%s" % (case_i(directive))
|
||||
|
||||
regex = "(%s)|(%s)|(%s)" % (case_i(directive),
|
||||
case_i("Include"),
|
||||
case_i("IncludeOptional"))
|
||||
@@ -637,6 +637,49 @@ class ApacheParser(object):
|
||||
|
||||
return ordered_matches
|
||||
|
||||
def handle_includes(self, known_matches=None):
|
||||
"""
|
||||
This method searches through the configuration tree for Include and
|
||||
IncludeOptional directives and includes them in the Augeas DOM tree
|
||||
recursively.
|
||||
|
||||
:param list known_matches: List of already known Augeas DOM paths of found
|
||||
include directives.
|
||||
"""
|
||||
|
||||
start = get_aug_path(self.loc["root"])
|
||||
regex = "(%s)|(%s)" % (case_i("Include"), case_i("IncludeOptional"))
|
||||
|
||||
all_matches = self.aug.match(
|
||||
"%s//*[self::directive=~regexp('%s')]" % (start, regex))
|
||||
|
||||
if known_matches and all([True for m in all_matches if m in known_matches]):
|
||||
# We already found everything previously
|
||||
return
|
||||
|
||||
# Always use the exclusion as we want to have the correct state
|
||||
matches = self._exclude_dirs(all_matches)
|
||||
|
||||
for match in matches:
|
||||
incpath = self.get_arg(match + "/arg")
|
||||
# Attempts to add a transform to the file if one does not already exist
|
||||
|
||||
# Standardize the include argument based on server root
|
||||
if not incpath.startswith("/"):
|
||||
# Normpath will condense ../
|
||||
incpath = os.path.normpath(os.path.join(self.root, incpath))
|
||||
else:
|
||||
incpath = os.path.normpath(incpath)
|
||||
|
||||
if os.path.isdir(incpath):
|
||||
self.parse_file(os.path.join(incpath, "*"))
|
||||
else:
|
||||
self.parse_file(incpath)
|
||||
|
||||
# Iterate. The results will be checked and the recursion broken when
|
||||
# everything has been found.
|
||||
self.handle_includes(all_matches)
|
||||
|
||||
def get_all_args(self, match):
|
||||
"""
|
||||
Tries to fetch all arguments for a directive. See get_arg.
|
||||
@@ -755,10 +798,10 @@ class ApacheParser(object):
|
||||
arg = os.path.normpath(arg)
|
||||
|
||||
# Attempts to add a transform to the file if one does not already exist
|
||||
if os.path.isdir(arg):
|
||||
self.parse_file(os.path.join(arg, "*"))
|
||||
else:
|
||||
self.parse_file(arg)
|
||||
#if os.path.isdir(arg):
|
||||
# self.parse_file(os.path.join(arg, "*"))
|
||||
#else:
|
||||
# self.parse_file(arg)
|
||||
|
||||
# Argument represents an fnmatch regular expression, convert it
|
||||
# Split up the path and convert each into an Augeas accepted regex
|
||||
|
||||
84
certbot-apache/certbot_apache/parserassert.py
Normal file
84
certbot-apache/certbot_apache/parserassert.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""Runtime assertions for two different parser implementations"""
|
||||
|
||||
def simpleassert(old_value, new_value):
|
||||
"""
|
||||
Simple assertion
|
||||
"""
|
||||
assert old_value == new_value
|
||||
|
||||
def legacy_assert_list(old_list, new_list):
|
||||
"""
|
||||
Used to assert that both of the lists contain the same values.
|
||||
"""
|
||||
|
||||
assert len(old_list) == len(new_list)
|
||||
assert all([True for ol in old_list if ol in new_list])
|
||||
|
||||
def legacy_assert_dir(old_result, new_result):
|
||||
"""
|
||||
Used to test and ensure that the new implementation search results matches
|
||||
the old implementation results. This test is intended to be used only to test
|
||||
the old Augeas implementation results against the ParserNode Augeas implementation.
|
||||
|
||||
This test expects the DirectiveNode to have Augeas path in its attribute dict
|
||||
"_metadata" with key "augpath".
|
||||
|
||||
"""
|
||||
|
||||
if new_result != "CERTBOT_PASS_ASSERT":
|
||||
return
|
||||
|
||||
assert len(old_result) == len(new_result)
|
||||
|
||||
matching = []
|
||||
|
||||
for oldres in old_result:
|
||||
match = [res for res in new_result if res._metadata["augpath"] == oldres]
|
||||
assert len(match) == 1
|
||||
|
||||
|
||||
def legacy_assert_args(old_result, new_result, parser):
|
||||
"""
|
||||
Used to test and ensure that returned parameter values are the same for
|
||||
the old and the new implementation.
|
||||
|
||||
Uses ApacheParser to actually fetch the parameter for the old result.
|
||||
|
||||
This assertion is structured this way because of how parser.get_arg() is
|
||||
currently used in the ApacheConfigurator, making it easier to test the
|
||||
results.
|
||||
"""
|
||||
|
||||
if isinstance(old_result, list):
|
||||
for old in old_result:
|
||||
oldarg = parser.get_arg(old_result)
|
||||
assert oldarg in new_result.parameters
|
||||
else:
|
||||
oldarg = parser.get_arg(old_result)
|
||||
assert oldarg in new_result.parameters
|
||||
|
||||
def assert_dir(first, second):
|
||||
"""
|
||||
Used to test that DirectiveNode results match for both implementations.
|
||||
"""
|
||||
|
||||
if "CERTBOT_PASS_ASSERT" in [first, second]:
|
||||
return
|
||||
|
||||
assert first.name == second.name
|
||||
assert first.parameters == second.parameters
|
||||
assert first.dirty == second.dirty
|
||||
|
||||
|
||||
def assert_block(first, second):
|
||||
"""
|
||||
Used to test that BlockNode results match for both implementations.
|
||||
"""
|
||||
|
||||
if "CERTBOT_PASS_ASSERT" in [first, second]:
|
||||
return
|
||||
|
||||
assert first.name == second.name
|
||||
assert first.parameters == second.parameters
|
||||
assert len(first.children) == len(second.children)
|
||||
assert first.dirty == second.dirty
|
||||
@@ -91,6 +91,7 @@ class ComplexParserTest(util.ParserTest):
|
||||
from certbot_apache import parser
|
||||
self.parser.add_dir(parser.get_aug_path(self.parser.loc["default"]),
|
||||
"Include", [arg])
|
||||
self.parser.handle_includes()
|
||||
if hit:
|
||||
self.assertTrue(self.parser.find_dir("FNMATCH_DIRECTIVE"))
|
||||
else:
|
||||
|
||||
86
certbot-apache/certbot_apache/tests/parsernode_test.py
Normal file
86
certbot-apache/certbot_apache/tests/parsernode_test.py
Normal file
@@ -0,0 +1,86 @@
|
||||
""" Tests for ParserNode interface """
|
||||
|
||||
import unittest
|
||||
|
||||
from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module
|
||||
|
||||
from certbot_apache import interfaces
|
||||
|
||||
|
||||
|
||||
class DummyCommentNode(interfaces.CommentNode):
|
||||
""" A dummy class implementing CommentNode interface """
|
||||
ancestor = None
|
||||
comment = ""
|
||||
dirty = False
|
||||
|
||||
def save(self, msg): # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
class DummyDirectiveNode(interfaces.DirectiveNode):
|
||||
""" A dummy class implementing DirectiveNode interface """
|
||||
ancestor = None
|
||||
parameters = tuple() # type: Tuple[str, ...]
|
||||
dirty = False
|
||||
enabled = True
|
||||
name = ""
|
||||
|
||||
def save(self, msg): # pragma: no cover
|
||||
pass
|
||||
|
||||
def set_parameters(self, parameters): # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
class DummyBlockNode(interfaces.BlockNode):
|
||||
""" A dummy class implementing BlockNode interface """
|
||||
ancestor = None
|
||||
parameters = tuple() # type: Tuple[str, ...]
|
||||
children = tuple() # type: Tuple[interfaces.ParserNode, ...]
|
||||
dirty = False
|
||||
enabled = True
|
||||
name = ""
|
||||
|
||||
def save(self, msg): # pragma: no cover
|
||||
pass
|
||||
|
||||
def add_child_block(self, name, arguments=None, position=None): # pragma: no cover
|
||||
pass
|
||||
|
||||
def add_child_directive(self, name, arguments=None, position=None): # pragma: no cover
|
||||
pass
|
||||
|
||||
def add_child_comment(self, comment="", position=None): # pragma: no cover
|
||||
pass
|
||||
|
||||
def find_blocks(self, name, exclude=True): # pragma: no cover
|
||||
pass
|
||||
|
||||
def find_directives(self, name, exclude=True): # pragma: no cover
|
||||
pass
|
||||
|
||||
def find_comments(self, comment, exact=False): # pragma: no cover
|
||||
pass
|
||||
|
||||
def delete_child(self, child): # pragma: no cover
|
||||
pass
|
||||
|
||||
def set_parameters(self, parameters): # pragma: no cover
|
||||
pass
|
||||
|
||||
def unsaved_files(self): # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
class ParserNodeTest(unittest.TestCase):
|
||||
"""Dummy placeholder test case for ParserNode interfaces"""
|
||||
|
||||
def test_dummy(self):
|
||||
dummyblock = DummyBlockNode()
|
||||
dummydirective = DummyDirectiveNode()
|
||||
dummycomment = DummyCommentNode()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
Reference in New Issue
Block a user