Repository: certbot/certbot

Issue:
Incompatible with ConfigArgParse 0.12.0
## My operating system is (include version):
Ubuntu 16.04

## I installed Certbot with (certbot-auto, OS package manager, pip, etc):
pip

## I ran this command and it produced this output:
An unexpected error occurred:
AttributeError: 'tuple' object has no attribute 'add'

## Certbot's behavior differed from what I expected because:
ConfigArgParse updated to version 0.12.0

## Here is a Certbot log showing the issue (if available):
```
# cat /tmp/tmpsCKLky 
2017-05-12 16:49:25,831:DEBUG:certbot.main:certbot version: 0.14.0
2017-05-12 16:49:25,832:DEBUG:certbot.main:Arguments: []
2017-05-12 16:49:25,832:DEBUG:certbot.main:Discovered plugins: PluginsRegistry(PluginEntryPoint#manual,PluginEntryPoint#null,PluginEntryPoint#standalone,PluginEntryPoint#webroot)
2017-05-12 16:49:25,835:DEBUG:certbot.log:Exiting abnormally:
Traceback (most recent call last):
  File "/usr/local/share/letsencrypt/env/bin/letsencrypt", line 11, in <module>
    sys.exit(main())
  File "/usr/local/share/letsencrypt/env/local/lib/python2.7/site-packages/certbot/main.py", line 729, in main
    args = cli.prepare_and_parse_args(plugins, cli_args)
  File "/usr/local/share/letsencrypt/env/local/lib/python2.7/site-packages/certbot/cli.py", line 1089, in prepare_and_parse_args
    helpful.add_deprecated_argument("--agree-dev-preview", 0)
  File "/usr/local/share/letsencrypt/env/local/lib/python2.7/site-packages/certbot/cli.py", line 741, in add_deprecated_argument
    self.parser.add_argument, argument_name, num_args)
  File "/usr/local/share/letsencrypt/env/local/lib/python2.7/site-packages/certbot/util.py", line 479, in add_deprecated_argument
    configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE.add(ShowWarning)
AttributeError: 'tuple' object has no attribute 'add'
```



Discussion:
+1, also seeing this problem.

The relevant command that is triggering this exception in my case is `sudo letsencrypt --apache -d <domain> -d <another domain>`. This same command has always worked in the past.
This is caused by the recent release of configargparse version 0.12.0.  The variable ACTION_TYPES_THAT_DONT_NEED_A_VALUE was changed from a set to a tuple in that release.

Resolving patch:
diff --git a/certbot/util.py b/certbot/util.py
--- a/certbot/util.py
+++ b/certbot/util.py
@@ -477,7 +477,13 @@ def __call__(self, unused1, unused2, unused3, option_string=None):
             sys.stderr.write(
                 "Use of {0} is deprecated.\n".format(option_string))
 
-    configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE.add(ShowWarning)
+    # In version 0.12.0 ACTION_TYPES_THAT_DONT_NEED_A_VALUE was changed from a
+    # set to a tuple.
+    if isinstance(configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE, set):
+        # pylint: disable=no-member
+        configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE.add(ShowWarning)
+    else:
+        configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE += (ShowWarning,)
     add_argument(argument_name, action=ShowWarning,
                  help=argparse.SUPPRESS, nargs=nargs)
 


Test patch:
diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py
--- a/certbot/tests/util_test.py
+++ b/certbot/tests/util_test.py
@@ -368,6 +368,29 @@ def test_help(self):
                 pass
         self.assertTrue("--old-option" not in stdout.getvalue())
 
+    def test_set_constant(self):
+        """Test when ACTION_TYPES_THAT_DONT_NEED_A_VALUE is a set.
+
+        This variable is a set in configargparse versions < 0.12.0.
+
+        """
+        self._test_constant_common(set)
+
+    def test_tuple_constant(self):
+        """Test when ACTION_TYPES_THAT_DONT_NEED_A_VALUE is a tuple.
+
+        This variable is a tuple in configargparse versions >= 0.12.0.
+
+        """
+        self._test_constant_common(tuple)
+
+    def _test_constant_common(self, typ):
+        with mock.patch("certbot.util.configargparse") as mock_configargparse:
+            mock_configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE = typ()
+            self._call("--old-option", 1)
+        self.assertEqual(
+            len(mock_configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE), 1)
+
 
 class EnforceLeValidity(unittest.TestCase):
     """Test enforce_le_validity."""