Repository: certbot/certbot

Issue:
Nginx parser needs add_or_set_server_directives
Right now Nginx's parser has an add_server_directives method, with a `replace` parameter. If `replace` is true, the directive must already exist in the config, and it will be replaced. If `replace` is false, the directive is added regardless of what exists in the config.

Since most directives are not allowed repeatedly, what we want in many cases is an `add_or_set_server_directives`, which will replace a directive if it's present*, and add it if it's not present.

*To be really tidy, we should say: If the directive is present with the correct value, leave it alone, i.e. don't change its location in the config.



Discussion:
Hey @diracdeltas, what would you think if I just changed the semantics of `replace=True` in [parser._add_directives](https://github.com/letsencrypt/letsencrypt/blob/master/letsencrypt-nginx/letsencrypt_nginx/parser.py#L469) so it doesn't error out if there's nothing to replace, and instead just adds the directive?

Actually thinking about this some more: since duplicate directives are almost always an error, perhaps instead we should change the semantics of replace=False so that if the directive exists and its value is equal to the one being set, no directive is added; otherwise error.

sgtm


Resolving patch:
diff --git a/letsencrypt-nginx/letsencrypt_nginx/parser.py b/letsencrypt-nginx/letsencrypt_nginx/parser.py
--- a/letsencrypt-nginx/letsencrypt_nginx/parser.py
+++ b/letsencrypt-nginx/letsencrypt_nginx/parser.py
@@ -257,6 +257,8 @@ def add_server_directives(self, filename, names, directives,
 
         ..note :: If replace is True, this raises a misconfiguration error
         if the directive does not already exist.
+        ..note :: If replace is False nothing gets added if an identical
+        block exists already.
 
         ..todo :: Doesn't match server blocks whose server_name directives are
             split across multiple conf files.
@@ -480,7 +482,9 @@ def _add_directives(block, directives, replace=False):
         if not replace:
             # We insert new directives at the top of the block, mostly
             # to work around https://trac.nginx.org/nginx/ticket/810
-            block.insert(0, directive)
+            # Only add directive if its not already in the block
+            if directive not in block:
+                block.insert(0, directive)
         else:
             changed = False
             if len(directive) == 0:


Test patch:
diff --git a/letsencrypt-nginx/letsencrypt_nginx/tests/parser_test.py b/letsencrypt-nginx/letsencrypt_nginx/tests/parser_test.py
--- a/letsencrypt-nginx/letsencrypt_nginx/tests/parser_test.py
+++ b/letsencrypt-nginx/letsencrypt_nginx/tests/parser_test.py
@@ -133,11 +133,11 @@ def test_add_server_directives(self):
         self.assertEqual(1, len(re.findall(ssl_re, dump)))
 
         server_conf = nparser.abs_path('server.conf')
-        nparser.add_server_directives(server_conf,
-                                      set(['alias', 'another.alias',
-                                           'somename']),
+        names = set(['alias', 'another.alias', 'somename'])
+        nparser.add_server_directives(server_conf, names,
                                       [['foo', 'bar'], ['ssl_certificate',
                                                         '/etc/ssl/cert2.pem']])
+        nparser.add_server_directives(server_conf, names, [['foo', 'bar']])
         self.assertEqual(nparser.parsed[server_conf],
                          [['ssl_certificate', '/etc/ssl/cert2.pem'],
                           ['foo', 'bar'],