Compare commits
5 Commits
test-power
...
parser_ref
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5d1509434 | ||
|
|
270754deff | ||
|
|
a83f9eb4e4 | ||
|
|
fed2264dac | ||
|
|
31a8d086fc |
192
certbot-apache/certbot_apache/augeasparser.py
Normal file
192
certbot-apache/certbot_apache/augeasparser.py
Normal file
@@ -0,0 +1,192 @@
|
||||
""" Tests for ParserNode interface """
|
||||
from certbot_apache import interfaces
|
||||
|
||||
from acme.magic_typing import Dict, Tuple # pylint: disable=unused-import, no-name-in-module
|
||||
|
||||
|
||||
class AugeasCommentNode(interfaces.CommentNode):
|
||||
""" Augeas implementation of CommentNode interface """
|
||||
ancestor = None
|
||||
comment = ""
|
||||
dirty = False
|
||||
_metadata = dict() # type: Dict[str, object]
|
||||
|
||||
def __init__(self, comment, ancestor=None):
|
||||
self.comment = comment
|
||||
self.ancestor = ancestor
|
||||
|
||||
def save(self, msg): # pragma: no cover
|
||||
pass
|
||||
|
||||
# Apache specific functionality
|
||||
|
||||
def get_metadata(self, key):
|
||||
""" Returns a metadata object
|
||||
|
||||
:param str key: Metadata object name to return
|
||||
:returns: Requested metadata object
|
||||
"""
|
||||
try:
|
||||
return self._metadata[key]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
class AugeasDirectiveNode(interfaces.DirectiveNode):
|
||||
""" Augeas implementation of DirectiveNode interface """
|
||||
ancestor = None
|
||||
parameters = tuple() # type: Tuple[str, ...]
|
||||
dirty = False
|
||||
enabled = True
|
||||
name = ""
|
||||
_metadata = dict() # type: Dict[str, object]
|
||||
|
||||
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")
|
||||
|
||||
# Apache specific functionality
|
||||
|
||||
def get_filename(self):
|
||||
"""Returns the filename where this directive exists on disk
|
||||
|
||||
:returns: File path to this node.
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
# Following is the real implementation when everything else is in place:
|
||||
# return apache_util.get_file_path(
|
||||
# self.parser.aug.get("/augeas/files%s/path" % apache_util.get_file_path(path)))
|
||||
return "CERTBOT_PASS_ASSERT"
|
||||
|
||||
def get_metadata(self, key):
|
||||
""" Returns a metadata object
|
||||
|
||||
:param str key: Metadata object name to return
|
||||
:returns: Requested metadata object
|
||||
"""
|
||||
try:
|
||||
return self._metadata[key]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def has_parameter(self, parameter, position=None):
|
||||
"""Checks if this ParserNode object has a supplied parameter. This check
|
||||
is case insensitive.
|
||||
|
||||
:param str parameter: Parameter value to look for
|
||||
:param position: Optional explicit position of parameter to look for
|
||||
|
||||
:returns: True if parameter is found
|
||||
:rtype: bool
|
||||
"""
|
||||
if position != None:
|
||||
return parameter.lower() == self.parameters[position].lower()
|
||||
|
||||
for param in self.parameters:
|
||||
if param.lower() == parameter.lower():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
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 = ""
|
||||
_metadata = dict() # type: Dict[str, object]
|
||||
|
||||
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"]
|
||||
|
||||
# Apache specific functionality
|
||||
|
||||
def get_filename(self):
|
||||
"""Returns the filename where this directive exists on disk
|
||||
|
||||
:returns: File path to this node.
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
# Following is the real implementation when everything else is in place:
|
||||
# return apache_util.get_file_path(
|
||||
# self.parser.aug.get("/augeas/files%s/path" %
|
||||
# apache_util.get_file_path(self.get_metadata("augeas_path")))
|
||||
return "CERTBOT_PASS_ASSERT"
|
||||
|
||||
def get_metadata(self, key):
|
||||
""" Returns a metadata object
|
||||
|
||||
:param str key: Metadata object name to return
|
||||
:returns: Requested metadata object
|
||||
"""
|
||||
try:
|
||||
return self._metadata[key]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def has_parameter(self, parameter, position=None):
|
||||
"""Checks if this ParserNode object has a supplied parameter. This check
|
||||
is case insensitive.
|
||||
|
||||
:param str parameter: Parameter value to look for
|
||||
:param position: Optional explicit position of parameter to look for
|
||||
|
||||
:returns: True if parameter is found
|
||||
:rtype: bool
|
||||
"""
|
||||
if position != None:
|
||||
return parameter.lower() == self.parameters[position].lower()
|
||||
|
||||
for param in self.parameters:
|
||||
if param.lower() == parameter.lower():
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -810,6 +810,28 @@ class ApacheConfigurator(common.Installer):
|
||||
|
||||
return (servername, serveraliases)
|
||||
|
||||
def _populate_vhost_names_v2(self, vhost):
|
||||
"""Helper function that populates the VirtualHost names.
|
||||
|
||||
:param host: In progress vhost whose names will be added
|
||||
:type host: :class:`~certbot_apache.obj.VirtualHost`
|
||||
|
||||
"""
|
||||
|
||||
servername_match = vhost.node.find_directives("ServerName",
|
||||
exclude=False)
|
||||
serveralias_match = vhost.node.find_directives("ServerAlias",
|
||||
exclude=False)
|
||||
|
||||
if servername_match:
|
||||
servername = servername_match[-1].parameters[-1]
|
||||
|
||||
if not vhost.modmacro:
|
||||
for alias in serveralias_match:
|
||||
for serveralias in alias.parameters:
|
||||
vhost.aliases.add(serveralias)
|
||||
vhost.name = servername
|
||||
|
||||
def _add_servernames(self, host):
|
||||
"""Helper function for get_virtual_hosts().
|
||||
|
||||
@@ -871,6 +893,52 @@ class ApacheConfigurator(common.Installer):
|
||||
self._add_servernames(vhost)
|
||||
return vhost
|
||||
|
||||
def _create_vhost_v2(self, node):
|
||||
"""Used by get_virtual_hosts to create vhost objects using ParserNode
|
||||
interfaces.
|
||||
|
||||
:param interfaces.BlockNode node: The BlockNode object of VirtualHost block
|
||||
|
||||
:returns: newly created vhost
|
||||
:rtype: :class:`~certbot_apache.obj.VirtualHost`
|
||||
"""
|
||||
addrs = set()
|
||||
for param in node.parameters:
|
||||
addrs.add(obj.Addr.fromstring(param))
|
||||
|
||||
is_ssl = False
|
||||
sslengine = node.find_directives("SSLEngine")
|
||||
if sslengine:
|
||||
for directive in sslengine:
|
||||
# TODO: apache-parser-v2
|
||||
# This search should be made wiser. (using other identificators)
|
||||
if directive.has_parameter("on", 0):
|
||||
is_ssl = True
|
||||
|
||||
# "SSLEngine on" might be set outside of <VirtualHost>
|
||||
# Treat vhosts with port 443 as ssl vhosts
|
||||
for addr in addrs:
|
||||
if addr.get_port() == "443":
|
||||
is_ssl = True
|
||||
|
||||
filename = node.get_filename()
|
||||
|
||||
if filename is None:
|
||||
return None
|
||||
|
||||
macro = False
|
||||
if node.find_directives("Macro"):
|
||||
macro = True
|
||||
|
||||
vhost_enabled = self.parser.parsed_in_original(filename)
|
||||
|
||||
vhost = obj.VirtualHost(filename, node.get_metadata("augeas_path"),
|
||||
addrs, is_ssl, vhost_enabled, modmacro=macro,
|
||||
node=node)
|
||||
|
||||
self._populate_vhost_names_v2(vhost)
|
||||
return vhost
|
||||
|
||||
def get_virtual_hosts(self):
|
||||
"""Returns list of virtual hosts found in the Apache configuration.
|
||||
|
||||
|
||||
476
certbot-apache/certbot_apache/interfaces.py
Normal file
476
certbot-apache/certbot_apache/interfaces.py
Normal file
@@ -0,0 +1,476 @@
|
||||
"""ParserNode interface for interacting with configuration tree.
|
||||
|
||||
General description
|
||||
-------------------
|
||||
|
||||
The ParserNode interfaces are designed to be able to contain all the parsing logic,
|
||||
while allowing their users to interact with the configuration tree in a Pythonic
|
||||
and well structured manner.
|
||||
|
||||
The structure allows easy traversal of the tree of ParserNodes. Each ParserNode
|
||||
stores a reference to its ancestor and immediate children, allowing the user to
|
||||
traverse the tree using built in interface methods as well as accessing the interface
|
||||
properties directly.
|
||||
|
||||
ParserNode interface implementation should stand between the actual underlying
|
||||
parser functionality and the business logic within Configurator code, interfacing
|
||||
with both. The ParserNode tree is a result of configuration parsing action.
|
||||
|
||||
ParserNode tree will be in charge of maintaining the parser state and hence the
|
||||
abstract syntax tree (AST). Interactions between ParserNode tree and underlying
|
||||
parser should involve only parsing the configuration files to this structure, and
|
||||
writing it back to the filesystem - while preserving the format including whitespaces.
|
||||
|
||||
For some implementations (Apache for example) it's important to keep track of and
|
||||
to use state information while parsing conditional blocks and directives. This
|
||||
allows the implementation to set a flag to parts of the parsed configuration
|
||||
structure as not being in effect in a case of unmatched conditional block. It's
|
||||
important to store these blocks in the tree as well in order to not to conduct
|
||||
destructive actions (failing to write back parts of the configuration) while writing
|
||||
the AST back to the filesystem.
|
||||
|
||||
The ParserNode tree is in charge of maintaining the its own structure while every
|
||||
child node fetched with find - methods or by iterating its list of children can be
|
||||
changed in place. When making changes the affected nodes should be flagged as "dirty"
|
||||
in order for the parser implementation to figure out the parts of the configuration
|
||||
that need to be written back to disk during the save() operation.
|
||||
|
||||
|
||||
Metadata
|
||||
--------
|
||||
|
||||
The metadata holds all the implementation specific attributes of the ParserNodes -
|
||||
things like the positional information related to the AST, file paths, whitespacing,
|
||||
and any other information relevant to the underlying parser engine.
|
||||
|
||||
Access to the metadata should be handled by implementation specific methods, allowing
|
||||
the Configurator functionality to access the underlying information where needed.
|
||||
A good example of this is file path of a node - something that is needed by the
|
||||
reverter functionality within the Configurator.
|
||||
|
||||
|
||||
Apache implementation
|
||||
---------------------
|
||||
|
||||
The Apache implementation of ParserNode interface requires some implementation
|
||||
specific functionalities that are not described by the interface itself.
|
||||
|
||||
Conditional blocks
|
||||
|
||||
Apache configuration can have conditional blocks, for example: <IfModule ...>,
|
||||
resulting the directives and subblocks within it being either enabled or disabled.
|
||||
While find_* interface methods allow including the disabled parts of the configuration
|
||||
tree in searches a special care needs to be taken while parsing the structure in
|
||||
order to reflect the active state of configuration.
|
||||
|
||||
Whitespaces
|
||||
|
||||
Each ParserNode object is responsible of storing its prepending whitespace characters
|
||||
in order to be able to write the AST back to filesystem like it was, preserving the
|
||||
format, this applies for parameters of BlockNode and DirectiveNode as well.
|
||||
When parameters of ParserNode are changed, the pre-existing whitespaces in the
|
||||
parameter sequence are discarded, as the general reason for storing them is to
|
||||
maintain the ability to write the configuration back to filesystem exactly like
|
||||
it was. This loses its meaning when we have to change the directives or blocks
|
||||
parameters for other reasons.
|
||||
|
||||
Searches and matching
|
||||
|
||||
Apache configuration is largely case insensitive, so the Apache implementation of
|
||||
ParserNode interface needs to provide the user means to match block and directive
|
||||
names and parameters in case insensitive manner. This does not apply to everything
|
||||
however, for example the parameters of a conditional statement may be case sensitive.
|
||||
For this reason the internal representation of data should not ignore the case.
|
||||
"""
|
||||
|
||||
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
|
||||
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 parameters 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. While the whitespaces for parameters are discarded when using
|
||||
this method, the whitespacing preceeding the ParserNode itself should be
|
||||
kept intact.
|
||||
|
||||
: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 parameters
|
||||
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 parameters used as an optional argument for some of the methods should
|
||||
be lists of strings that are applicable parameters for each specific BlockNode
|
||||
or DirectiveNode type. As an example, for a following configuration example:
|
||||
|
||||
<VirtualHost *:80>
|
||||
...
|
||||
</VirtualHost>
|
||||
|
||||
The node type would be BlockNode, name would be 'VirtualHost' and its parameters
|
||||
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 its
|
||||
parameters would be: ['alias_module', '/usr/lib/apache2/modules/mod_alias.so']
|
||||
|
||||
The applicable parameters are dependent on the underlying configuration language
|
||||
and its grammar.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def add_child_block(self, name, parameters=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. The preceeding
|
||||
whitespaces should not be added based on the ancestor or siblings for the
|
||||
newly created object. This is to match the current behavior of the legacy
|
||||
parser implementation.
|
||||
|
||||
:param str name: The name of the child node to add
|
||||
:param list parameters: list of parameters 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, parameters=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. The
|
||||
preceeding whitespaces should not be added based on the ancestor or siblings
|
||||
for the newly created object. This is to match the current behavior of the
|
||||
legacy parser implementation.
|
||||
|
||||
|
||||
:param str name: The name of the child node to add
|
||||
:param list parameters: list of parameters 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. The
|
||||
preceeding whitespaces should not be added based on the ancestor or siblings
|
||||
for the newly created object. This is to match the current behavior of the
|
||||
legacy parser implementation.
|
||||
|
||||
|
||||
: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. The lookup
|
||||
should be case insensitive.
|
||||
|
||||
: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. The lookup should
|
||||
be case insensitive.
|
||||
|
||||
: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. The lookup should be case sensitive.
|
||||
|
||||
: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. While the whitespaces for parameters are discarded when using
|
||||
this method, the whitespacing preceeding the ParserNode itself should be
|
||||
kept intact.
|
||||
|
||||
: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.
|
||||
"""
|
||||
@@ -124,7 +124,7 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
|
||||
strip_name = re.compile(r"^(?:.+://)?([^ :$]*)")
|
||||
|
||||
def __init__(self, filep, path, addrs, ssl, enabled, name=None,
|
||||
aliases=None, modmacro=False, ancestor=None):
|
||||
aliases=None, modmacro=False, ancestor=None, node=None):
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
"""Initialize a VH."""
|
||||
@@ -137,6 +137,7 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
|
||||
self.enabled = enabled
|
||||
self.modmacro = modmacro
|
||||
self.ancestor = ancestor
|
||||
self.node = node
|
||||
|
||||
def get_names(self):
|
||||
"""Return a set of all names."""
|
||||
|
||||
232
certbot-apache/certbot_apache/tests/parsernode_test.py
Normal file
232
certbot-apache/certbot_apache/tests/parsernode_test.py
Normal file
@@ -0,0 +1,232 @@
|
||||
""" Tests for ParserNode interface """
|
||||
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from acme.magic_typing import Dict, Tuple # pylint: disable=unused-import, no-name-in-module
|
||||
|
||||
from certbot_apache import augeasparser
|
||||
from certbot_apache import interfaces
|
||||
|
||||
from certbot_apache.tests import util
|
||||
|
||||
|
||||
|
||||
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, parameters=None, position=None): # pragma: no cover
|
||||
pass
|
||||
|
||||
def add_child_directive(self, name, parameters=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(util.ApacheTest):
|
||||
"""Test cases for ParserNode interface"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ParserNodeTest, self).__init__(*args, **kwargs)
|
||||
self.mock_nodes = dict() # type: Dict[str, interfaces.ParserNode]
|
||||
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super(ParserNodeTest, self).setUp()
|
||||
|
||||
self.config = util.get_apache_configurator(
|
||||
self.config_path, self.vhost_path, self.config_dir, self.work_dir)
|
||||
self.vh_truth = util.get_vh_truth(
|
||||
self.temp_dir, "debian_apache_2_4/multiple_vhosts")
|
||||
|
||||
def test_dummy(self):
|
||||
dummyblock = DummyBlockNode()
|
||||
dummydirective = DummyDirectiveNode()
|
||||
dummycomment = DummyCommentNode()
|
||||
|
||||
def _create_mock_vhost_nodes(self, servername, serveraliases, addrs):
|
||||
"""Create a mock VirtualHost nodes"""
|
||||
|
||||
nodes = {
|
||||
"VirtualHost": augeasparser.AugeasBlockNode("VirtualHost", tuple(addrs)),
|
||||
"ServerName": augeasparser.AugeasDirectiveNode("ServerName",
|
||||
(servername,)),
|
||||
"ServerAlias": augeasparser.AugeasDirectiveNode("ServerAlias",
|
||||
tuple(serveraliases)),
|
||||
"Macro": augeasparser.AugeasDirectiveNode("Macro", ("variable", "value",)),
|
||||
"SSLEngine": augeasparser.AugeasDirectiveNode("SSLEngine", ("on",))
|
||||
}
|
||||
return nodes
|
||||
|
||||
def mock_find_directives(self, name, exclude=True): # pylint: disable=unused-argument
|
||||
"""
|
||||
Mocks BlockNode.find_directives() and returns values defined in class
|
||||
variable self.mock_nodes, set by the test case
|
||||
"""
|
||||
try:
|
||||
return self.mock_nodes[name]
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
def test_create_vhost_v2_nonssl(self):
|
||||
nodes = self._create_mock_vhost_nodes("example.com",
|
||||
["a1.example.com", "a2.example.com"],
|
||||
["*:80"])
|
||||
|
||||
nodes["VirtualHost"].find_directives = self.mock_find_directives
|
||||
self.mock_nodes = {"ServerName": [nodes["ServerName"]],
|
||||
"ServerAlias": [nodes["ServerAlias"]]}
|
||||
vhost = self.config._create_vhost_v2(nodes["VirtualHost"]) # pylint: disable=protected-access
|
||||
self.assertEqual(vhost.name, "example.com")
|
||||
self.assertTrue("a1.example.com" in vhost.aliases)
|
||||
self.assertTrue("a2.example.com" in vhost.aliases)
|
||||
self.assertEqual(len(vhost.aliases), 2)
|
||||
self.assertEqual(len(vhost.addrs), 1)
|
||||
self.assertFalse(vhost.ssl)
|
||||
self.assertFalse(vhost.modmacro)
|
||||
|
||||
def test_create_vhost_v2_macro(self):
|
||||
nodes = self._create_mock_vhost_nodes("example.com",
|
||||
["a1.example.com", "a2.example.com"],
|
||||
["*:80"])
|
||||
|
||||
nodes["VirtualHost"].find_directives = self.mock_find_directives
|
||||
self.mock_nodes = {"ServerName": [nodes["ServerName"]],
|
||||
"ServerAlias": [nodes["ServerAlias"]],
|
||||
"Macro": [nodes["Macro"]]}
|
||||
vhost = self.config._create_vhost_v2(nodes["VirtualHost"]) # pylint: disable=protected-access
|
||||
self.assertEqual(vhost.name, None)
|
||||
self.assertEqual(vhost.aliases, set())
|
||||
self.assertFalse(vhost.ssl)
|
||||
self.assertTrue(vhost.modmacro)
|
||||
|
||||
def test_create_vhost_v2_ssl_port(self):
|
||||
nodes = self._create_mock_vhost_nodes("example.com",
|
||||
["a1.example.com", "a2.example.com"],
|
||||
["*:443"])
|
||||
|
||||
nodes["VirtualHost"].find_directives = self.mock_find_directives
|
||||
self.mock_nodes = {"ServerName": [nodes["ServerName"]],
|
||||
"ServerAlias": [nodes["ServerAlias"]]}
|
||||
vhost = self.config._create_vhost_v2(nodes["VirtualHost"]) # pylint: disable=protected-access
|
||||
self.assertTrue(vhost.ssl)
|
||||
self.assertFalse(vhost.modmacro)
|
||||
|
||||
def test_create_vhost_v2_sslengine(self):
|
||||
nodes = self._create_mock_vhost_nodes("example.com",
|
||||
["a1.example.com", "a2.example.com"],
|
||||
["*:80"])
|
||||
|
||||
nodes["VirtualHost"].find_directives = self.mock_find_directives
|
||||
self.mock_nodes = {"ServerName": [nodes["ServerName"]],
|
||||
"ServerAlias": [nodes["ServerAlias"]],
|
||||
"SSLEngine": [nodes["SSLEngine"]]}
|
||||
vhost = self.config._create_vhost_v2(nodes["VirtualHost"]) # pylint: disable=protected-access
|
||||
self.assertTrue(vhost.ssl)
|
||||
self.assertFalse(vhost.modmacro)
|
||||
|
||||
def test_create_vhost_v2_no_filename(self):
|
||||
nodes = self._create_mock_vhost_nodes("example.com",
|
||||
["a1.example.com", "a2.example.com"],
|
||||
["*:80"])
|
||||
nodes["VirtualHost"].find_directives = self.mock_find_directives
|
||||
self.mock_nodes = {"ServerName": [nodes["ServerName"]],
|
||||
"ServerAlias": [nodes["ServerAlias"]],
|
||||
"SSLEngine": [nodes["SSLEngine"]]}
|
||||
filename = "certbot_apache.augeasparser.AugeasBlockNode.get_filename"
|
||||
with mock.patch(filename) as mock_filename:
|
||||
mock_filename.return_value = None
|
||||
vhost = self.config._create_vhost_v2(nodes["VirtualHost"]) # pylint: disable=protected-access
|
||||
self.assertEqual(vhost, None)
|
||||
|
||||
def test_comment_node_creation(self):
|
||||
comment = augeasparser.AugeasCommentNode("This is a comment")
|
||||
comment._metadata["augeas_path"] = "/whatever" # pylint: disable=protected-access
|
||||
self.assertEqual(comment.get_metadata("augeas_path"), "/whatever")
|
||||
self.assertEqual(comment.get_metadata("something_else"), None)
|
||||
self.assertEqual(comment.comment, "This is a comment")
|
||||
|
||||
def test_directive_node_creation(self):
|
||||
directive = augeasparser.AugeasDirectiveNode("DIRNAME", ("p1", "p2",))
|
||||
directive._metadata["augeas_path"] = "/whatever" # pylint: disable=protected-access
|
||||
self.assertEqual(directive.get_metadata("augeas_path"), "/whatever")
|
||||
self.assertEqual(directive.get_metadata("something_else"), None)
|
||||
self.assertEqual(directive.name, "DIRNAME")
|
||||
self.assertEqual(directive.parameters, ("p1", "p2",))
|
||||
self.assertTrue(directive.has_parameter("P1", 0))
|
||||
self.assertFalse(directive.has_parameter("P2", 0))
|
||||
self.assertFalse(directive.has_parameter("P3"))
|
||||
self.assertTrue(directive.has_parameter("P2"))
|
||||
self.assertEqual(directive.get_filename(), "CERTBOT_PASS_ASSERT")
|
||||
|
||||
def test_block_node_creation(self):
|
||||
block = augeasparser.AugeasBlockNode("BLOCKNAME", ("first", "SECOND",))
|
||||
block._metadata["augeas_path"] = "/whatever" # pylint: disable=protected-access
|
||||
self.assertEqual(block.get_metadata("augeas_path"), "/whatever")
|
||||
self.assertEqual(block.get_metadata("something_else"), None)
|
||||
self.assertEqual(block.name, "BLOCKNAME")
|
||||
self.assertEqual(block.parameters, ("first", "SECOND",))
|
||||
self.assertFalse(block.has_parameter("second", 0))
|
||||
self.assertFalse(block.has_parameter("SECOND", 0))
|
||||
self.assertFalse(block.has_parameter("third"))
|
||||
self.assertTrue(block.has_parameter("FIRST"))
|
||||
self.assertEqual(block.get_filename(), "CERTBOT_PASS_ASSERT")
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
Reference in New Issue
Block a user