Merge pull request #3218 from certbot/nginx-dirty-write-only

Only write nginx config files if they've been modified
This commit is contained in:
schoen
2016-06-27 14:07:51 -07:00
committed by GitHub
4 changed files with 35 additions and 8 deletions
+19 -6
View File
@@ -1,4 +1,5 @@
"""Very low-level nginx config parser based on pyparsing.""" """Very low-level nginx config parser based on pyparsing."""
# Forked from https://github.com/fatiherikli/nginxparser (MIT Licensed)
import copy import copy
import logging import logging
import string import string
@@ -81,8 +82,7 @@ class RawNginxDumper(object):
yield b.pop(0) # indentation yield b.pop(0) # indentation
if not b: if not b:
continue continue
key = b.pop(0) key, values = b.pop(0), b.pop(0)
values = b.pop(0)
if isinstance(key, list): if isinstance(key, list):
yield "".join(key) + '{' yield "".join(key) + '{'
@@ -91,9 +91,9 @@ class RawNginxDumper(object):
yield line yield line
yield '}' yield '}'
else: else:
if isinstance(key, str) and key.strip() == '#': if isinstance(key, str) and key.strip() == '#': # comment
yield key + values yield key + values
else: else: # assignment
gap = "" gap = ""
# Sometimes the parser has stuck some gap whitespace in here; # Sometimes the parser has stuck some gap whitespace in here;
# if so rotate it into gap # if so rotate it into gap
@@ -163,6 +163,7 @@ class UnspacedList(list):
def __init__(self, list_source): def __init__(self, list_source):
# ensure our argument is not a generator, and duplicate any sublists # ensure our argument is not a generator, and duplicate any sublists
self.spaced = copy.deepcopy(list(list_source)) self.spaced = copy.deepcopy(list(list_source))
self.dirty = False
# Turn self into a version of the source list that has spaces removed # Turn self into a version of the source list that has spaces removed
# and all sub-lists also UnspacedList()ed # and all sub-lists also UnspacedList()ed
@@ -198,20 +199,24 @@ class UnspacedList(list):
item, spaced_item = self._coerce(x) item, spaced_item = self._coerce(x)
self.spaced.insert(self._spaced_position(i), spaced_item) self.spaced.insert(self._spaced_position(i), spaced_item)
list.insert(self, i, item) list.insert(self, i, item)
self.dirty = True
def append(self, x): def append(self, x):
item, spaced_item = self._coerce(x) item, spaced_item = self._coerce(x)
self.spaced.append(spaced_item) self.spaced.append(spaced_item)
list.append(self, item) list.append(self, item)
self.dirty = True
def extend(self, x): def extend(self, x):
item, spaced_item = self._coerce(x) item, spaced_item = self._coerce(x)
self.spaced.extend(spaced_item) self.spaced.extend(spaced_item)
list.extend(self, item) list.extend(self, item)
self.dirty = True
def __add__(self, other): def __add__(self, other):
l = copy.deepcopy(self) l = copy.deepcopy(self)
l.extend(other) l.extend(other)
l.dirty = True
return l return l
def pop(self, _i=None): def pop(self, _i=None):
@@ -231,16 +236,24 @@ class UnspacedList(list):
item, spaced_item = self._coerce(value) item, spaced_item = self._coerce(value)
self.spaced.__setitem__(self._spaced_position(i), spaced_item) self.spaced.__setitem__(self._spaced_position(i), spaced_item)
list.__setitem__(self, i, item) list.__setitem__(self, i, item)
self.dirty = True
def __delitem__(self, i): def __delitem__(self, i):
self.spaced.__delitem__(self._spaced_position(i)) self.spaced.__delitem__(self._spaced_position(i))
list.__delitem__(self, i) list.__delitem__(self, i)
self.dirty = True
def __deepcopy__(self, unused_memo): def __deepcopy__(self, memo):
l = UnspacedList(self[:]) l = UnspacedList(self[:])
l.spaced = copy.deepcopy(self.spaced) l.spaced = copy.deepcopy(self.spaced, memo=memo)
l.dirty = self.dirty
return l return l
def is_dirty(self):
"""Recurse through the parse tree to figure out if any sublists are dirty"""
if self.dirty:
return True
return any((isinstance(x, list) and x.is_dirty() for x in self))
def _spaced_position(self, idx): def _spaced_position(self, idx):
"Convert from indexes in the unspaced list to positions in the spaced one" "Convert from indexes in the unspaced list to positions in the spaced one"
+4 -1
View File
@@ -205,11 +205,12 @@ class NginxParser(object):
raise errors.NoInstallationError( raise errors.NoInstallationError(
"Could not find configuration root") "Could not find configuration root")
def filedump(self, ext='tmp'): def filedump(self, ext='tmp', lazy=True):
"""Dumps parsed configurations into files. """Dumps parsed configurations into files.
:param str ext: The file extension to use for the dumped files. If :param str ext: The file extension to use for the dumped files. If
empty, this overrides the existing conf files. empty, this overrides the existing conf files.
:param bool lazy: Only write files that have been modified
""" """
# Best-effort atomicity is enforced above us by reverter.py # Best-effort atomicity is enforced above us by reverter.py
@@ -218,6 +219,8 @@ class NginxParser(object):
if ext: if ext:
filename = filename + os.path.extsep + ext filename = filename + os.path.extsep + ext
try: try:
if lazy and not tree.is_dirty():
continue
out = nginxparser.dumps(tree) out = nginxparser.dumps(tree)
logger.debug('Writing nginx conf tree to %s:\n%s', filename, out) logger.debug('Writing nginx conf tree to %s:\n%s', filename, out)
with open(filename, 'w') as _file: with open(filename, 'w') as _file:
@@ -231,6 +231,17 @@ class TestUnspacedList(unittest.TestCase):
del ul3[2] del ul3[2]
self.assertEqual(ul3, ["some", "things", "why", "did", "whether"]) self.assertEqual(ul3, ["some", "things", "why", "did", "whether"])
def test_is_dirty(self):
self.assertEqual(False, self.ul2.is_dirty())
ul3 = UnspacedList([])
ul3.append(self.ul)
self.assertEqual(False, self.ul.is_dirty())
self.assertEqual(True, ul3.is_dirty())
ul4 = UnspacedList([[1], [2, 3, 4]])
self.assertEqual(False, ul4.is_dirty())
ul4[1][2] = 5
self.assertEqual(True, ul4.is_dirty())
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() # pragma: no cover unittest.main() # pragma: no cover
@@ -66,7 +66,7 @@ class NginxParserTest(util.NginxTest):
def test_filedump(self): def test_filedump(self):
nparser = parser.NginxParser(self.config_path, self.ssl_options) nparser = parser.NginxParser(self.config_path, self.ssl_options)
nparser.filedump('test') nparser.filedump('test', lazy=False)
# pylint: disable=protected-access # pylint: disable=protected-access
parsed = nparser._parse_files(nparser.abs_path( parsed = nparser._parse_files(nparser.abs_path(
'sites-enabled/example.com.test')) 'sites-enabled/example.com.test'))