From 9c8cdd05da46128f4676216d1407b24af41f736c Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 9 Dec 2024 16:19:30 -0800 Subject: [PATCH 1/2] remove the windows installer --- certbot-ci/MANIFEST.in | 1 - .../__init__.py | 0 .../conftest.py | 38 --- .../py.typed | 0 .../test_main.py | 69 ----- certbot/certbot/_internal/renewal.py | 2 - certbot/docs/contributing.rst | 5 +- tools/pinning/current/pyproject.toml | 1 - tools/venv.py | 1 - windows-installer/.gitignore | 2 - windows-installer/assets/certbot.ico | Bin 183198 -> 0 bytes windows-installer/assets/preamble.py | 12 - windows-installer/assets/renew-down.ps1 | 6 - windows-installer/assets/renew-up.ps1 | 17 -- windows-installer/assets/run.bat | 31 -- windows-installer/assets/template.nsi | 285 ------------------ windows-installer/setup.py | 45 --- .../windows_installer/__init__.py | 0 .../windows_installer/construct.py | 151 ---------- 19 files changed, 1 insertion(+), 665 deletions(-) delete mode 100644 certbot-ci/windows_installer_integration_tests/__init__.py delete mode 100644 certbot-ci/windows_installer_integration_tests/conftest.py delete mode 100644 certbot-ci/windows_installer_integration_tests/py.typed delete mode 100644 certbot-ci/windows_installer_integration_tests/test_main.py delete mode 100644 windows-installer/.gitignore delete mode 100644 windows-installer/assets/certbot.ico delete mode 100644 windows-installer/assets/preamble.py delete mode 100644 windows-installer/assets/renew-down.ps1 delete mode 100644 windows-installer/assets/renew-up.ps1 delete mode 100644 windows-installer/assets/run.bat delete mode 100644 windows-installer/assets/template.nsi delete mode 100644 windows-installer/setup.py delete mode 100644 windows-installer/windows_installer/__init__.py delete mode 100644 windows-installer/windows_installer/construct.py diff --git a/certbot-ci/MANIFEST.in b/certbot-ci/MANIFEST.in index 64d68999e..7be27c3dc 100644 --- a/certbot-ci/MANIFEST.in +++ b/certbot-ci/MANIFEST.in @@ -1,4 +1,3 @@ recursive-include certbot_integration_tests/assets * include certbot_integration_tests/py.typed include snap_integration_tests/py.typed -include windows_installer_integration_tests/py.typed diff --git a/certbot-ci/windows_installer_integration_tests/__init__.py b/certbot-ci/windows_installer_integration_tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-ci/windows_installer_integration_tests/conftest.py b/certbot-ci/windows_installer_integration_tests/conftest.py deleted file mode 100644 index c0917013d..000000000 --- a/certbot-ci/windows_installer_integration_tests/conftest.py +++ /dev/null @@ -1,38 +0,0 @@ -# type: ignore -""" -General conftest for pytest execution of all integration tests lying -in the window_installer_integration tests package. -As stated by pytest documentation, conftest module is used to set on -for a directory a specific configuration using built-in pytest hooks. - -See https://docs.pytest.org/en/latest/reference.html#hook-reference -""" - -import os - -ROOT_PATH = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - - -def pytest_addoption(parser): - """ - Standard pytest hook to add options to the pytest parser. - :param parser: current pytest parser that will be used on the CLI - """ - parser.addoption('--installer-path', - default=os.path.join(ROOT_PATH, 'windows-installer', 'build', - 'nsis', 'certbot-beta-installer-win_amd64.exe'), - help='set the path of the windows installer to use, default to ' - 'CERTBOT_ROOT_PATH\\windows-installer\\build\\nsis\\certbot-beta-installer-win_amd64.exe') # pylint: disable=line-too-long - parser.addoption('--allow-persistent-changes', action='store_true', - help='needs to be set, and confirm that the test will make persistent changes on this machine') # pylint: disable=line-too-long - - -def pytest_configure(config): - """ - Standard pytest hook used to add a configuration logic for each node of a pytest run. - :param config: the current pytest configuration - """ - if not config.option.allow_persistent_changes: - raise RuntimeError('This integration test would install Certbot on your machine. ' - 'Please run it again with the `--allow-persistent-changes` ' - 'flag set to acknowledge.') diff --git a/certbot-ci/windows_installer_integration_tests/py.typed b/certbot-ci/windows_installer_integration_tests/py.typed deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-ci/windows_installer_integration_tests/test_main.py b/certbot-ci/windows_installer_integration_tests/test_main.py deleted file mode 100644 index 635335522..000000000 --- a/certbot-ci/windows_installer_integration_tests/test_main.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Module executing integration tests for the windows installer.""" -import os -import re -import subprocess -import time -from typing import Any - -import pytest - - -@pytest.mark.skipif(os.name != 'nt', reason='Windows installer tests must be run on Windows.') -def test_it(request: pytest.FixtureRequest) -> None: - try: - subprocess.check_call(['certbot', '--version']) - except (subprocess.CalledProcessError, OSError): - pass - else: - raise AssertionError('Expect certbot to not be available in the PATH.') - - try: - # Install certbot - subprocess.check_call([request.config.option.installer_path, '/S']) - - # Assert certbot is installed and runnable - output = subprocess.check_output(['certbot', '--version'], universal_newlines=True) - assert re.match(r'^certbot \d+\.\d+\.\d+.*$', - output), 'Flag --version does not output a version.' - - # Assert renew task is installed and ready - output = _ps('(Get-ScheduledTask -TaskName "Certbot Renew Task").State', - capture_stdout=True) - assert output.strip() == 'Ready' - - # Assert renew task is working - now = time.time() - _ps('Start-ScheduledTask -TaskName "Certbot Renew Task"') - - status = 'Running' - while status != 'Ready': - status = _ps('(Get-ScheduledTask -TaskName "Certbot Renew Task").State', - capture_stdout=True).strip() - time.sleep(1) - - log_path = os.path.join('C:\\', 'Certbot', 'log', 'letsencrypt.log') - - modification_time = os.path.getmtime(log_path) - assert now < modification_time, 'Certbot log file has not been modified by the renew task.' - - with open(log_path) as file_h: - data = file_h.read() - assert 'no renewal failures' in data, 'Renew task did not execute properly.' - - finally: - # Sadly this command cannot work in non interactive mode: uninstaller will - # ask explicitly permission in an UAC prompt - # print('Uninstalling Certbot ...') - # uninstall_path = _ps('(gci "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"' # pylint: disable=line-too-long - # ' | foreach { gp $_.PSPath }' - # ' | ? { $_ -match "Certbot" }' - # ' | select UninstallString)' - # '.UninstallString', capture_stdout=True) - # subprocess.check_call([uninstall_path, '/S']) - pass - - -def _ps(powershell_str: str, capture_stdout: bool = False) -> Any: - fn = subprocess.check_output if capture_stdout else subprocess.check_call - return fn(['powershell.exe', '-c', powershell_str], # type: ignore[operator] - universal_newlines=True) diff --git a/certbot/certbot/_internal/renewal.py b/certbot/certbot/_internal/renewal.py index 62e31f865..bad314538 100644 --- a/certbot/certbot/_internal/renewal.py +++ b/certbot/certbot/_internal/renewal.py @@ -568,8 +568,6 @@ def handle_renewal_request(config: configuration.NamespaceConfig) -> Tuple[list, raise errors.Error( f"{len(renew_failures)} renew failure(s), {len(parse_failures)} parse failure(s)") - # Windows installer integration tests rely on handle_renewal_request behavior here. - # If the text below changes, these tests will need to be updated accordingly. logger.debug("no renewal failures") return (renewed_domains, failed_domains) diff --git a/certbot/docs/contributing.rst b/certbot/docs/contributing.rst index 2af5f3016..cf2927656 100644 --- a/certbot/docs/contributing.rst +++ b/certbot/docs/contributing.rst @@ -238,8 +238,6 @@ certbot-apache and certbot-nginx client code to configure specific web servers certbot-dns-* client code to configure DNS providers -windows installer - Installs Certbot on Windows and is built using the files in windows-installer/ Plugin-architecture ------------------- @@ -587,8 +585,7 @@ Certbot's dependencies We attempt to pin all of Certbot's dependencies whenever we can for reliability and consistency. Some of the places we have Certbot's dependencies pinned -include our snaps, Docker images, Windows installer, CI, and our development -environments. +include our snaps, Docker images, CI, and our development environments. In most cases, the file where dependency versions are specified is ``tools/requirements.txt``. The one exception to this is our "oldest" tests diff --git a/tools/pinning/current/pyproject.toml b/tools/pinning/current/pyproject.toml index ae7df57a9..9443d5b44 100644 --- a/tools/pinning/current/pyproject.toml +++ b/tools/pinning/current/pyproject.toml @@ -35,7 +35,6 @@ certbot-apache = {path = "../../../certbot-apache", extras = ["dev"]} certbot = {path = "../../../certbot", extras = ["all"]} acme = {path = "../../../acme", extras = ["docs", "test"]} letstest = {path = "../../../letstest"} -windows-installer = {path = "../../../windows-installer"} # Extra dependencies # As of writing this, cython is a build dependency of pyyaml. Since there diff --git a/tools/venv.py b/tools/venv.py index 4044258ef..d8165f089 100755 --- a/tools/venv.py +++ b/tools/venv.py @@ -46,7 +46,6 @@ REQUIREMENTS = [ ] if sys.platform == 'win32': - REQUIREMENTS.append('-e windows-installer') REQUIREMENTS.remove('-e certbot-apache') REQUIREMENTS.remove('-e certbot-compatibility-test') diff --git a/windows-installer/.gitignore b/windows-installer/.gitignore deleted file mode 100644 index a1a48d6b8..000000000 --- a/windows-installer/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build -build.* diff --git a/windows-installer/assets/certbot.ico b/windows-installer/assets/certbot.ico deleted file mode 100644 index 364c32098ecf32457010a7170f4d068249847104..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 183198 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