Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite argument parsing #4493

Closed
bmw opened this issue Apr 12, 2017 · 17 comments
Closed

Rewrite argument parsing #4493

bmw opened this issue Apr 12, 2017 · 17 comments
Labels
area: code health feature request needs-update priority: significant Issues with higher than average priority that do not need to be in the current milestone.
Milestone

Comments

@bmw
Copy link
Member

bmw commented Apr 12, 2017

We currently use the largely unmaintained configargparse and a number of hacks like magic default values and reaching into the internals of configargparse/argparse to get the behavior we want. We should rewrite our argument parsing to use another library like click or fire.

cc @erikrose, @ohemorange

@erikrose
Copy link
Member

I've had excellent experiences with click. See https://github.com/mozilla/dxr/blob/master/dxr/cli/__init__.py for the top-level entrypoint in DXR's click-based CLI. click is particularly good at subcommands.

@ohemorange
Copy link
Contributor

I don't know anything about click, but my friend wrote fire so we could probably get good support. I'm all for cleaner code everywhere!

@erikrose
Copy link
Member

erikrose commented Apr 12, 2017

Then we should probably make a pro/con list and see what pops out! Nice to have 2 good choices.

@anarcat
Copy link

anarcat commented Jan 15, 2019

i don't know if click will cover the use cases currently covered by configargparse. first, it doesn't support config files out of the box - that might be done by a third party module like click_config_file. that config file support doesn't support writing config files the way certbot currently does either.

then click is way more than a commandline parser - it's a whole TUI (text user interface) driver. it hijacks the main control flow of your program and makes testing ... different, for example. it supports progress bars and ANSI art and whatnot. it is very opiniated about how things should be and you might find it difficult to work with if you deviate from canon.

fire is much simpler, but also has the limitation of not working with config files.

another option people often favor is docopt which also doesn't support config files (let alone writing them) but there is some sample code to parse config files that is surprisingly clean.

there aren't that many options here - rewriting the commandline parser is a huge deal in certbot. argparse, while it's weird and complicated, is one of the best things we have right now. i wonder if the best approach might not be to fix the issues in configargparse instead of rewriting half of certbot. ;) and there's been some action on the issue of finding a new maintainer on that side, so I wouldn't give up on it already...

@bmw
Copy link
Member Author

bmw commented Jan 15, 2019

Thanks for the thoughts @anarcat! For what it's worth, we don't use configargparse for writing configuration files and instead use configobj.

@anarcat
Copy link

anarcat commented Jan 15, 2019

well that makes things easier. for what it's worth, I ended up ditching all those methods for my own implementation and just rolled my own. I suggest you take that into consideration...

https://gitlab.com/anarcat/undertime/blob/acd4a7c73f7e9c727a8a572884a46bcb696682c9/undertime#L102

it's around 60 lines of code, but i think it's quite elegant, all things considered. it uses pyyaml to parse the config file, but that's easy to override... heck, it's short enough that I can just paste it here:

class ConfigAction(argparse.Action):
    """add configuration file to current defaults"""
    def __init__(self, *args, **kwargs):
        """the config action is a search path, so a list, so one or more argument"""
        kwargs['nargs'] = '+'
        super().__init__(*args, **kwargs)

    def __call__(self, parser, ns, values, option):
        """change defaults for the namespace, still allows overriding
        from commandline options"""
        for path in values:
            parser.set_defaults(**self.parse_config(path))

    def parse_config(self, path):
        """abstract implementation of config file parsing, should be overriden in subclasses"""
        raise NotImplementedError()


class YamlConfigAction(ConfigAction):
    """YAML config file parser action"""
    def parse_config(self, path):
        try:
            with open(os.path.expanduser(path), 'r') as handle:
                return yaml.safe_load(handle)
        except (FileNotFoundError, yaml.parser.ParserError) as e:
            raise argparse.ArgumentError(self, e)


class ConfigArgumentParser(argparse.ArgumentParser):
    """argument parser which supports parsing extra config files

    Config files specified on the commandline through the
    YamlConfigAction arguments modify the default values on the
    spot. If a default is specified when adding an argument, it also
    gets immediately loaded.

    This will typically be used in a subclass, like this:

            self.add_argument('--config', action=YamlConfigAction, default=self.default_config())

    """

    def _add_action(self, action):
        # this overrides the add_argument() routine, which is where
        # actions get registered. it is done so we can properly load
        # the default config file before the action actually gets
        # fired. Ideally, we'd load the default config only if the
        # action *never* gets fired (but still setting defaults for
        # the namespace) but argparse doesn't give us that opportunity
        # (and even if it would, it wouldn't retroactively change the
        # Namespace object in parse_args() so it wouldn't work).
        action = super()._add_action(action)
        if isinstance(action, ConfigAction) and action.default is not None:
            # fire the action, later calls can override defaults
            try:
                action(self, None, action.default, None)
            except argparse.ArgumentError:
                # ignore errors from missing default
                pass

    def default_config(self):
        """handy shortcut to detect commonly used config paths"""
        return [os.path.join(os.environ.get('XDG_CONFIG_HOME', '~/.config/'), self.prog + '.yml')]

I simply use it like this:

parser = ConfigArgumentParser()
parser.add_argument('--config', action=YamlConfigAction, default=ConfigArgumentParser.default_config())

This will load the config from ~/.config/argv0.yml as default values for other parameters on the commandline, or from whatever config file specified by the --config argument. It is overridable on the commandline and everything, but doesn't write its own config file either.

Anyways, I hope that helps... The TL;DR: of all the above is it's easy to preset defaults from a config file for simple parsers (parser.set_defaults() with a dict) but it becomes possibly more complicated with sub-parsers, in which case you might want to have add more tweaks to the above...

@ronhanson
Copy link

If you are having issues with Configargparse, or new feature requests, feel free to fill bug reports and do PR. I will try my best to move the project forward and fix the existing bugs.

@anarcat
Copy link

anarcat commented Jan 31, 2019

@ronhanson there are many issues in certbot that are ultimately upstream issues in configargparse. for example, bw2/ConfigArgParse#104 and #6049 are two issues here specific to the project...

@stale
Copy link

stale bot commented Jan 31, 2020

We've made a lot of changes to Certbot since this issue was opened. If you still have this issue with an up-to-date version of Certbot, can you please add a comment letting us know? This helps us to better see what issues are still affecting our users. If there is no activity in the next 30 days, this issue will be automatically closed.

@stale stale bot added the needs-update label Jan 31, 2020
@bmw
Copy link
Member Author

bmw commented Jan 31, 2020

If we ever get to this, it'll honestly probably be years from now, but I personally still think this should be done. Our current CLI parsing is very complex, relies on a bunch of hacky custom code, and has caused a number of issues for us (see some of the issues referencing this one for a few examples).

@stale stale bot removed the needs-update label Jan 31, 2020
bmw added a commit that referenced this issue Dec 2, 2020
Fixes #8495.

To further explain the problem here, `modify_kwargs_for_default_detection` as called in `add` is simplistic and doesn't always work. See #6164 for one other example.

In this case, were bitten by the code https://github.com/certbot/certbot/blob/d1e7404358c05734aaf436ef3c9d709029d62b09/certbot/certbot/_internal/cli/helpful.py#L393-L395

The action used for deprecated arguments isn't in `ZERO_ARG_ACTIONS` so it assumes that all deprecated flags take one parameter.

Rather than trying to fix this function (which I think can only realistically be fixed by #4493), I took the approach that was previously used in `HelpfulArgumentParser.add_deprecated_argument` of bypassing this extra logic entirely. I adapted that function to now call `HelpfulArgumentParser.add` as well for consistency and to make testing easier.

* Rename deprecated arg action class

* Skip extra parsing for deprecated arguments

* Add back test of --manual-public-ip-logging-ok

* Add changelog entry
bmw added a commit that referenced this issue Dec 2, 2020
Fixes #8495.

To further explain the problem here, `modify_kwargs_for_default_detection` as called in `add` is simplistic and doesn't always work. See #6164 for one other example.

In this case, were bitten by the code https://github.com/certbot/certbot/blob/d1e7404358c05734aaf436ef3c9d709029d62b09/certbot/certbot/_internal/cli/helpful.py#L393-L395

The action used for deprecated arguments isn't in `ZERO_ARG_ACTIONS` so it assumes that all deprecated flags take one parameter.

Rather than trying to fix this function (which I think can only realistically be fixed by #4493), I took the approach that was previously used in `HelpfulArgumentParser.add_deprecated_argument` of bypassing this extra logic entirely. I adapted that function to now call `HelpfulArgumentParser.add` as well for consistency and to make testing easier.

* Rename deprecated arg action class

* Skip extra parsing for deprecated arguments

* Add back test of --manual-public-ip-logging-ok

* Add changelog entry

(cherry picked from commit 5f73274)
bmw added a commit that referenced this issue Dec 2, 2020
Fixes #8495.

To further explain the problem here, `modify_kwargs_for_default_detection` as called in `add` is simplistic and doesn't always work. See #6164 for one other example.

In this case, were bitten by the code https://github.com/certbot/certbot/blob/d1e7404358c05734aaf436ef3c9d709029d62b09/certbot/certbot/_internal/cli/helpful.py#L393-L395

The action used for deprecated arguments isn't in `ZERO_ARG_ACTIONS` so it assumes that all deprecated flags take one parameter.

Rather than trying to fix this function (which I think can only realistically be fixed by #4493), I took the approach that was previously used in `HelpfulArgumentParser.add_deprecated_argument` of bypassing this extra logic entirely. I adapted that function to now call `HelpfulArgumentParser.add` as well for consistency and to make testing easier.

* Rename deprecated arg action class

* Skip extra parsing for deprecated arguments

* Add back test of --manual-public-ip-logging-ok

* Add changelog entry

(cherry picked from commit 5f73274)
adferrand pushed a commit that referenced this issue Dec 3, 2020
Fixes #8495.

To further explain the problem here, `modify_kwargs_for_default_detection` as called in `add` is simplistic and doesn't always work. See #6164 for one other example.

In this case, were bitten by the code https://github.com/certbot/certbot/blob/d1e7404358c05734aaf436ef3c9d709029d62b09/certbot/certbot/_internal/cli/helpful.py#L393-L395

The action used for deprecated arguments isn't in `ZERO_ARG_ACTIONS` so it assumes that all deprecated flags take one parameter.

Rather than trying to fix this function (which I think can only realistically be fixed by #4493), I took the approach that was previously used in `HelpfulArgumentParser.add_deprecated_argument` of bypassing this extra logic entirely. I adapted that function to now call `HelpfulArgumentParser.add` as well for consistency and to make testing easier.

* Rename deprecated arg action class

* Skip extra parsing for deprecated arguments

* Add back test of --manual-public-ip-logging-ok

* Add changelog entry

(cherry picked from commit 5f73274)
@bmw bmw removed this from the Wishlist milestone Dec 8, 2020
@bmw bmw added the priority: significant Issues with higher than average priority that do not need to be in the current milestone. label Dec 8, 2020
orangepizza added a commit to orangepizza/certbot that referenced this issue Dec 16, 2020
* Implements support for ECDSA keys. Fixes certbot#2163.

Thanks to @pahrohfit and @Tomoyuki-GH for previous efforts to implement
suport for this.

Co-Authored-By: Robert Dailey <rob@wargam.es>
Co-Authored-By: Tomoyuki-GH <55397638+Tomoyuki-GH@users.noreply.github.com>

* Handle unexpected key type migration. (certbot#8435)

Fixes certbot#8365

This PR adds a control when `certbot certonly` or `certbot run` are called for a certificate that already exists and would eventually be replaced. As described in certbot#8365, this control is here to ensure that the user will not modify the key type of their certificate (eg. ECDSA to RSA) without an explicit approval (set explicitly `--cert-name` and `--key-type`), since RSA is the default if not specified.

* Handle unexpected key type migration.

* Update certbot-ci/certbot_integration_tests/certbot_tests/test_main.py

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

* Add certbot renew --key-type test (certbot#8447)

* Test certbot renew --key-type

* Fix typo

* Use better asserts. Added notes to style guide. (certbot#8451)

* Add --dns-server option in run_acme_server (certbot#7722)

Fixes certbot#7717

This PR adds a `--dns-server` option to the `run_acme_server` test tool, in order to provide an arbitrary DNS server to Pebble or Boulder for the integration tests.

I also take this occasion to make `run_acme_server` a real CLI tool using argparse, and set the `--server-type` (default `pebble`) option as well.

* Set --dns-server flag in run_acme_server

* Default to pebble

* Add documentation

* Configure also Boulder

* cli: improve Obtaining/Renewing wording (certbot#8395)

* cli: improve Obtaining/Renewing wording

* dont use logger, and use new phrasing

* .display_util.notify: dont wrap

As this function is supposed to be an analogue for print, we do not want
it to wrap by default.

* Add certbot-dns-rfc2136 integration testing (certbot#8448)

* tests: add certbot-dns-rfc2136 integration tests

* dont use 'with' form of socket.socket

fixes py2 crash

* address some feedback:

- conftest: make DNS server a global resource
- conftest: add dns_xdist parameter into node config
- conftest: add --dns-server=bind flag
- conftest: if configured, point the ACME server to the DNS server
- dnsserver: make it sort-of compatible with xdist (future-proofing)
- context: parameterize dns-rfc2136 credentials file (future proofing)
- context: reduce dns-rfc2136 propagation time to speed up tests
- tox: add a integration-dns-rfc2136 target
- rfc2136: add a test/zone for subdelegation
- rfc2136: skip tests if no DNS server is configured

* try add integration-dns-rfc2136 to CI

* mock recursive dns via RPZ

* update --dns-server args and tox.ini args

* address more feedback:

- dns_server: rename rfc2136 creds file to .tpl
- dns_server: dont vary dns server port, instead we will vary zone names (certbot#8455)
- dns_server: log error if bind9 fails to stop cleanly
- dns_server: replace assert with raise
- context: remove redundant _worker_id
- context: remove redundant cleanup override
- context: fix seek/flush in credentials context manager
- context: rename skip_if_no_server -> ...bind_server
- context: add newline EOF

* conftest: document _setup_primary_node sideeffects

* ci: rfc2136-integration from standard->nightly

* fix _stop_bind (function was renamed to stop)

* ignore errors from shutil.rmtree during cleanup

* dns_server: check for crash while polling

* remove --dry-run from rfc2136 test

* import print_function

* certbot-ci: fix py2 crash in dns_server

* Read files as binary in crypto_util for crypto.load_certificate. (certbot#8371)

* Flesh out ECDSA documentation (certbot#8464)

* Changelog tweaks.

* Add ECDSA documentation

* Fix typo

* Add Python 3.9 support and tests (certbot#8460)

Fixes certbot#8134.

* Test on Python 3.9.

* Mention Python 3.9 support in changelog.

* s/\( *'Pro.*3\.\)8\(',\)/\18\2\n\19\2/

* undo changes to tox.ini

* Move more tests to Python 3.9

* Update PyYAML and packages which pinned it back

* Upgrade typed-ast

* Use <= to "pin" dnspython

* Fix lint by telling pylint it cannot be trusted

* Disable mypy on RFC plugin

* add comment about <= support

* Fix link typo in README (certbot#8476)

* nginx: fix Unicode crash on Python 2 (certbot#8480)

* nginx: fix py2 unicode sandwich

The nginx parser would crash when saving configuraitons containing
Unicode, because py2's `str` type does not support Unicode.

This change fixes that crash by ensuring that a string type supporting
Unicode is used in both Python 2 and Python 3.

* nginx: add unicode to the integration test config

* update CHANGELOG

* Update changelog for 1.10.0 release

* Release 1.10.0

* Add contents to certbot/CHANGELOG.md for next version

* Bump version to 1.11.0

* Fix changelog typo (certbot#8488)

* fix changelog typo

* remove empty entry

* Deprecate certbot-auto and remove tests

* Completely deprecate certbot-auto

* DeaDeactivate centos6/oraclelinux6 tests

* Remove tests assets

* Remove another test

* Revert "Remove tests assets"

This reverts commit e603afe.

* Undo certbot-auto changes and remove centos6 tests

* Don't deprecate certbot-auto quite yet

* Remove centos6 test farm tests

* undo changes to test farm test scripts

* Deprecate certbot-auto and remove tests

* Completely deprecate certbot-auto

* DeaDeactivate centos6/oraclelinux6 tests

* Remove tests assets

* Remove another test

* Revert "Remove tests assets"

This reverts commit e603afe.

(cherry picked from commit ff3a07d)

* Undo certbot-auto changes and remove centos6 tests

* Don't deprecate certbot-auto quite yet

* Remove centos6 test farm tests

* undo changes to test farm test scripts

(cherry picked from commit e5113d5)

* Fix add deprecated argument (certbot#8500)

Fixes certbot#8495.

To further explain the problem here, `modify_kwargs_for_default_detection` as called in `add` is simplistic and doesn't always work. See certbot#6164 for one other example.

In this case, were bitten by the code https://github.com/certbot/certbot/blob/d1e7404358c05734aaf436ef3c9d709029d62b09/certbot/certbot/_internal/cli/helpful.py#L393-L395

The action used for deprecated arguments isn't in `ZERO_ARG_ACTIONS` so it assumes that all deprecated flags take one parameter.

Rather than trying to fix this function (which I think can only realistically be fixed by certbot#4493), I took the approach that was previously used in `HelpfulArgumentParser.add_deprecated_argument` of bypassing this extra logic entirely. I adapted that function to now call `HelpfulArgumentParser.add` as well for consistency and to make testing easier.

* Rename deprecated arg action class

* Skip extra parsing for deprecated arguments

* Add back test of --manual-public-ip-logging-ok

* Add changelog entry

* Fix changelog typo (certbot#8497)

Co-authored-by: Adrien Ferrand <ferrand.ad@gmail.com>

* Fix add deprecated argument (certbot#8500) (certbot#8501)

Fixes certbot#8495.

To further explain the problem here, `modify_kwargs_for_default_detection` as called in `add` is simplistic and doesn't always work. See certbot#6164 for one other example.

In this case, were bitten by the code https://github.com/certbot/certbot/blob/d1e7404358c05734aaf436ef3c9d709029d62b09/certbot/certbot/_internal/cli/helpful.py#L393-L395

The action used for deprecated arguments isn't in `ZERO_ARG_ACTIONS` so it assumes that all deprecated flags take one parameter.

Rather than trying to fix this function (which I think can only realistically be fixed by certbot#4493), I took the approach that was previously used in `HelpfulArgumentParser.add_deprecated_argument` of bypassing this extra logic entirely. I adapted that function to now call `HelpfulArgumentParser.add` as well for consistency and to make testing easier.

* Rename deprecated arg action class

* Skip extra parsing for deprecated arguments

* Add back test of --manual-public-ip-logging-ok

* Add changelog entry

(cherry picked from commit 5f73274)

* Update changelog for 1.10.1 release

* Release 1.10.1

* Add contents to certbot/CHANGELOG.md for next version

* Bump version to 1.11.0

* cli: clean up `certbot renew` summary (certbot#8503)

* cli: clean up `certbot renew` summary

- Unduplicate output which was being sent to both stdout and stderr
- Don't use IDisplay.notification to buffer output
- Remove big "DRY RUN" guards above and below, instead change language
  to "renewal" or "simulated renewal"
- Reword "Attempting to renew cert ... produced an unexpected error"
  to be more concise.

* add newline to docstring

Co-authored-by: ohemorange <ebportnoy@gmail.com>

Co-authored-by: ohemorange <ebportnoy@gmail.com>

* Update both main VA and remote VA to use the provided DNS server (certbot#8467)

* dns-google: improve credentials error message (certbot#8482)

This adds a 'Error parsing credentials file ...' wrapper to any errors
raised inside certbot-dns-google's usage of oauth2client, to make it
obvious to the user where the problem lies.

* Removed some unused imports. (certbot#8424)

These were not annotated as something that should be ignored, and the test-suite
passes with these changes.

* snap: disable the "user site-packages directory" (certbot#8509)

Although Certbot is a classic snap, it shouldn't load Python code from
the host system. This change prevents packages being loaded from the
"user site-packages directory" (PEP-370). i.e. Certbot will no longer
load DNS plugins installed via `pip install --user certbot-dns-*`.

* add coverage testing to dns-rfc2136 integration (certbot#8469)

* add coverage testing to dns-rfc2136 integration

* add coverage rule for certbot/* as well

* Completely deprecate certbot-auto (certbot#8489)

Fixes certbot#8296

* Completely deprecate certbot-auto

* Add changelog

* Deprecate support for Python 2 (certbot#8491)

Fixes certbot#8388

* Deprecate support for Python 2

* Ignore deprecation warning

* Update certbot/CHANGELOG.md

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

* Add reminders to update documentation (certbot#8518)

* Add documentation PR checklist item.

* Update contributing doc

* Avoid --system-site-packages during the snap build by preparing a venv with pipstrap that already includes wheel (certbot#8445)

This PR proposes an alternative configuration for the snap build that avoid the need to use `--system-site-package` when constructing the virtual environment in the snap.

The rationale of `--system-site-package` was that by default, snapcraft creates a virtual environment without `wheel` installed in it. However we need it to build the wheels like `cryptography` on ARM architectures. Sadly there is not way to instruct snapcraft to install some build dependencies in the virtual environment before it kicks in the build phase itself, without overriding that entire phase (which is possible with `parts.override-build`).

The alternative proposed here is to not override the entire build part, but just add some preparatory steps that will be done before the main actions handled by the `python` snap plugin. To do so, I take advantage of the `--upgrade` flag available for the `venv` module in Python 3. This allows to reuse a preexisting virtual environment, and upgrade its component. Adding a flag to the `venv` call is possible in snapcraft, thanks to the `SNAPCRAFT_PYTHON_VENV_ARGS` environment variable (and it is already used to set the `--system-site-package`).

Given `SNAPCRAFT_PYTHON_VENV_ARGS` set to `--upgrade` , we configure the build phase as follows:
* create the virtual environment ourselves in the expected place (`SNAPCRAFT_PART_INSTALL`)
* leverage `tools/pipstrap.py` to install `setuptools`, `pip`, and of course, `wheel`
* let the standard build operations kick in with a call to `snapcraftctl build`: at that point the `--upgrade` flag will be appended to the standard virtual environment creation, reusing our crafted venv instead of creating a new one.

This approach has also the advantage to invoke `pipstrap.py` as it is done for the other deployable artifacts, and for the PR validations, reducing risks of shifts between the various deployment methods.

* Deprecate support of Apache 2.2 in certbot-apache (certbot#8516)

Fixes certbot#8462

* Deprecate support of Apache 2.2 in certbot-apache

* Add a changelog

* Add finish_release flags and CLI parsing (certbot#8522)

* Setup a timeout to the remote snap build process (certbot#8484)

This PR adds a `--timeout` flag to `tools/snap/build_remote.py` in order to fail the process if the time execution reaches the provided timeout. It is set to 5h30 on the relevant Azure job, while the job itself has a timeout of 6h managed on Azure side. This allows a slightly better output for these jobs when the snapcraft build stales for any reason.

* add OS package warning (certbot#8533)

* Make our test farm tests instances self-destruct (certbot#8536)

* remove unused user data

* have instance self-destruct in case cleanup fails

* correct kwargs

* fix param order

* remove CentOS 6 cruft from test farm tests (certbot#8534)

* Add path to certbot executable in debug log (certbot#8538)

* Enable again build isolation with proper pinning of build dependencies (certbot#8443)

Fixes certbot#8256

First let's sum up the problem to solve. We disabled the build isolation available in pip>=19 because it could potential break certbot build without a control on our side. Basically builds are not reproductible. Indeed the build isolation triggers build of PEP-517 enabled transitive dependencies (like `cryptography`) with the build dependencies defined in their `pyproject.toml`. For `cryptography` in particular these requirements include `setuptools>=40.6.0`, and quite logically pip will install the latest version of `setuptools` for the build. And when `setuptools` broke with the version 50, our build did the same.

But disabling the build isolation is not a long term solution, as more and more project will migrate on this approach and it basically provides a lot of benefit in how dependencies are built.

The ideal solution would be to be able to apply version constraints on our side on the build dependencies, in order to pin `setuptools` for instance, and decide precisely when we upgrade to a newer version. However for now pip does not provide a mechanism for that (like a `--build-constraint` flag or propagation of existing `--constraint` flag).

Until I saw pypa/pip#9081 and pypa/pip#8439.

Apart the fact that pypa/pip#9081 shows that pip maintainers are working on this issue, it explains how pip works regarding PEP-517 and infers which workaround can be used to still pin the build dependencies. It turns out that pip invokes itself in each build isolation to install the build dependencies. It means that even if some flags (like `--constraint`) are not explicitly passed to the pip sub call, the global environment remains, in particular the environment variables.

Thus it is known that every pip flag can alternatively be set by environment variable using the following pattern for the variable name: `PIP_[FLAG_NAME_UPPERCASE]`. So for `--constraint`, it is `PIP_CONSTRAINT`. And so you can pass a constraint file to the pip sub call through that mechanism.

I made some tests with a constraint file containing pinning for `setuptools`: indeed under isolation zone, the constraint file has been honored and the provided pinned version has been used to build the dependencies (I tested it with `cryptography`).

Finally this PR takes advantage of this mechanism, by setting `PIP_CONSTRAINT` to `pip_install`, the snap building process, the Dockerfiles and the windows installer building process.

I also extracted out the requirements of the new `pipstrap.py` to be reusable in these various build processes.

* Use workaround to fix build requirements in build isolation, and renable build isolation

* Clean imports in pipstrap

* Externalize pipstrap reqs to be reusable

* Inject pipstrap constraints during pip_install

* Update docker build

* Update snapcraft build

* Prepare installer build

* Fix pipstrap constraints in snap build

* Add back --no-build-cache option in Docker images build

* Update snap/snapcraft.yaml

* Use proper flags with pip

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

* Added certbot-ci to lint section. Silenced and fixed linting warnings. (certbot#8450)

* remove reference to letsencrypt(-auto) (certbot#8531)

* Clean up certbot-auto docs (certbot#8532)

Fixes certbot#8519.

I left the `certbot-auto` docs in `install.rst` to avoid breaking links and to help propagate information about our changes there. I moved it closer to the bottom of the doc though since I think our documentation about OS packages and Docker is more helpful to most people.

* clean up certbot-auto docs

* add more info to changelog

* remove more certbot-auto references

Co-authored-by: Mads Jensen <mje@inducks.org>
Co-authored-by: Robert Dailey <rob@wargam.es>
Co-authored-by: Tomoyuki-GH <55397638+Tomoyuki-GH@users.noreply.github.com>
Co-authored-by: Adrien Ferrand <adferrand@users.noreply.github.com>
Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
Co-authored-by: alexzorin <alex@zorin.id.au>
Co-authored-by: Brad Warren <bmw@eff.org>
Co-authored-by: ohemorange <ebportnoy@gmail.com>
Co-authored-by: Adrien Ferrand <ferrand.ad@gmail.com>
Co-authored-by: alexzorin <alex@zor.io>
Co-authored-by: osirisinferi <github@flut.demon.nl>
@ohemorange
Copy link
Contributor

When we do this, we should keep in mind that verbose is a count argument, and therefore can be given with no arguments, or with a number in the cli.ini file. Then, we can deprecate the unlisted verbose-level argument.

@wgreenberg
Copy link
Collaborator

looking into this a bit, at first it seemed like click would be a great choice, but as mentioned earlier in this thread it's very opinionated. for example, it requires strict separation of arguments between commands and subcommands, e.g. certbot -d foo.com certonly means -d belongs to the certbot command, and thus you wouldn't be able to do certbot certonly -d foo.com. there's workarounds for this (see pallets/click#108), but nonetheless it'd be an annoying hack we'd have to pepper in everywhere.

fire looks like a convenient way to get a CLI up and running on a smaller project, but it doesn't look like it supports a lot of our use cases. for example, it doesn't seem to support multiple flags of the same type (-d foo.com -d bar.org), and it seems you can only set help text through docstrings.

i'll keep looking into the click hack and see if i can make it more palatable.

@bmw
Copy link
Member Author

bmw commented Mar 24, 2023

Ooo interesting. Thanks for doing that research! I'm very curious if any of these libraries help with our "default detection" stuff we've got going on.

Also, I think the Python CLI landscape has shifted a bit since this issue was initially created. Most of these are false leads, but I found https://python.libhunt.com/categories/181-command-line-application-development which lists all the libraries I think I've ever heard anyone talk about. In addition to docopt which was mentioned above, I've also heard good things about typer which is based on click.

@bmw
Copy link
Member Author

bmw commented Mar 24, 2023

Also also, I haven't looked into how hacky the workaround for click is, but in case you hadn't seen it already, I wanted to share that our support for arguments either on certbot or the subcommand is a hack on argparse too. See

def determine_verb(self) -> None:
"""Determines the verb/subcommand provided by the user.
This function works around some of the limitations of argparse.
"""
if "-h" in self.args or "--help" in self.args:
# all verbs double as help arguments; don't get them confused
self.verb = "help"
return
for i, token in enumerate(self.args):
if token in self.VERBS:
verb = token
if verb == "auth":
verb = "certonly"
if verb == "everything":
verb = "run"
self.verb = verb
self.args.pop(i)
return
self.verb = "run"

@wgreenberg
Copy link
Collaborator

wgreenberg commented Apr 12, 2023

updating this: although i ended up getting click to be flexible enough to handle certbot's (shall we say) freeform handling of CLI args, unfortunately argparse is built into some of our plugin interfaces, and so any migration away from it would almost certainly be a backwards compatibility-breaking change. since that would necessitate a major version change, i'll punt on it for now and instead focus on patching the existing argparse system to fix #9598

Copy link

We've made a lot of changes to Certbot since this issue was opened. If you still have this issue with an up-to-date version of Certbot, can you please add a comment letting us know? This helps us to better see what issues are still affecting our users. If there is no activity in the next 30 days, this issue will be automatically closed.

Copy link

This issue has been closed due to lack of activity, but if you think it should be reopened, please open a new issue with a link to this one and we'll take a look.

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale May 12, 2024
@zoracon zoracon added this to the 4.0 milestone Nov 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: code health feature request needs-update priority: significant Issues with higher than average priority that do not need to be in the current milestone.
Projects
None yet
Development

No branches or pull requests

7 participants