mirror of
https://github.com/certbot/certbot.git
synced 2026-08-03 03:52:02 +02:00
Replace boulder tests with pebble (#9918)
Pebble 2.5.1 supports OCSP stapling, so we can finally replace all boulder tests/harnesses with the much simpler pebble setup. Closes #9898 * Remove unused `--acme-server` argument Since this argument is never set and always defaults to 'pebble', just remove it to simplify assumptions about which test server's being used. * Remove boulder option from integration tests Now that pebble supports all of our test cases, we can move off of the much more complicated boulder test harness. * pebble_artifacts: bump to latest pebble release * pebble_artifacts: fix download path * certbot-ci: unzip pebble assets * CI: rip out windows tests/jobs * tox.ini: rm outdated Windows comment Co-authored-by: Brad Warren <bmw@users.noreply.github.com> * ci: rm redundant integration test Co-authored-by: Brad Warren <bmw@users.noreply.github.com> * acme_server: raise error if proxy and http-01 port are both set * acme_server: rm vestigial preterimate commands stuff --------- Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
This commit is contained in:
co-authored by
Brad Warren
parent
2a41402f2a
commit
873f979a25
@@ -19,34 +19,26 @@ jobs:
|
|||||||
TOXENV: py311
|
TOXENV: py311
|
||||||
linux-isolated:
|
linux-isolated:
|
||||||
TOXENV: 'isolated-acme,isolated-certbot,isolated-apache,isolated-cloudflare,isolated-digitalocean,isolated-dnsimple,isolated-dnsmadeeasy,isolated-gehirn,isolated-google,isolated-linode,isolated-luadns,isolated-nsone,isolated-ovh,isolated-rfc2136,isolated-route53,isolated-sakuracloud,isolated-nginx'
|
TOXENV: 'isolated-acme,isolated-certbot,isolated-apache,isolated-cloudflare,isolated-digitalocean,isolated-dnsimple,isolated-dnsmadeeasy,isolated-gehirn,isolated-google,isolated-linode,isolated-luadns,isolated-nsone,isolated-ovh,isolated-rfc2136,isolated-route53,isolated-sakuracloud,isolated-nginx'
|
||||||
linux-boulder-v2-integration-certbot-oldest:
|
linux-integration-certbot-oldest:
|
||||||
PYTHON_VERSION: 3.8
|
PYTHON_VERSION: 3.8
|
||||||
TOXENV: integration-certbot-oldest
|
TOXENV: integration-certbot-oldest
|
||||||
ACME_SERVER: boulder-v2
|
linux-integration-nginx-oldest:
|
||||||
linux-boulder-v2-integration-nginx-oldest:
|
|
||||||
PYTHON_VERSION: 3.8
|
PYTHON_VERSION: 3.8
|
||||||
TOXENV: integration-nginx-oldest
|
TOXENV: integration-nginx-oldest
|
||||||
ACME_SERVER: boulder-v2
|
# python 3.8 integration tests are not run here because they're run as
|
||||||
linux-boulder-v2-py38-integration:
|
# part of the standard test suite
|
||||||
PYTHON_VERSION: 3.8
|
linux-py39-integration:
|
||||||
TOXENV: integration
|
|
||||||
ACME_SERVER: boulder-v2
|
|
||||||
linux-boulder-v2-py39-integration:
|
|
||||||
PYTHON_VERSION: 3.9
|
PYTHON_VERSION: 3.9
|
||||||
TOXENV: integration
|
TOXENV: integration
|
||||||
ACME_SERVER: boulder-v2
|
linux-py310-integration:
|
||||||
linux-boulder-v2-py310-integration:
|
|
||||||
PYTHON_VERSION: 3.10
|
PYTHON_VERSION: 3.10
|
||||||
TOXENV: integration
|
TOXENV: integration
|
||||||
ACME_SERVER: boulder-v2
|
linux-py311-integration:
|
||||||
linux-boulder-v2-py311-integration:
|
|
||||||
PYTHON_VERSION: 3.11
|
PYTHON_VERSION: 3.11
|
||||||
TOXENV: integration
|
TOXENV: integration
|
||||||
ACME_SERVER: boulder-v2
|
linux-py312-integration:
|
||||||
linux-boulder-v2-py312-integration:
|
|
||||||
PYTHON_VERSION: 3.12
|
PYTHON_VERSION: 3.12
|
||||||
TOXENV: integration
|
TOXENV: integration
|
||||||
ACME_SERVER: boulder-v2
|
|
||||||
nginx-compat:
|
nginx-compat:
|
||||||
TOXENV: nginx_compat
|
TOXENV: nginx_compat
|
||||||
linux-integration-rfc2136:
|
linux-integration-rfc2136:
|
||||||
|
|||||||
@@ -55,65 +55,6 @@ jobs:
|
|||||||
- bash: |
|
- bash: |
|
||||||
set -e && tools/docker/test.sh $(dockerTag) $DOCKER_ARCH
|
set -e && tools/docker/test.sh $(dockerTag) $DOCKER_ARCH
|
||||||
displayName: Run integration tests for Docker images
|
displayName: Run integration tests for Docker images
|
||||||
- job: installer_build
|
|
||||||
pool:
|
|
||||||
vmImage: windows-2019
|
|
||||||
steps:
|
|
||||||
- task: UsePythonVersion@0
|
|
||||||
inputs:
|
|
||||||
versionSpec: 3.9
|
|
||||||
architecture: x64
|
|
||||||
addToPath: true
|
|
||||||
- script: |
|
|
||||||
python -m venv venv
|
|
||||||
venv\Scripts\python tools\pip_install.py -e windows-installer
|
|
||||||
displayName: Prepare Windows installer build environment
|
|
||||||
- script: |
|
|
||||||
venv\Scripts\construct-windows-installer
|
|
||||||
displayName: Build Certbot installer
|
|
||||||
- task: CopyFiles@2
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(System.DefaultWorkingDirectory)/windows-installer/build/nsis
|
|
||||||
contents: '*.exe'
|
|
||||||
targetFolder: $(Build.ArtifactStagingDirectory)
|
|
||||||
- task: PublishPipelineArtifact@1
|
|
||||||
inputs:
|
|
||||||
path: $(Build.ArtifactStagingDirectory)
|
|
||||||
# If we change the artifact's name, it should also be changed in tools/create_github_release.py
|
|
||||||
artifact: windows-installer
|
|
||||||
displayName: Publish Windows installer
|
|
||||||
- job: installer_run
|
|
||||||
dependsOn: installer_build
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
win2019:
|
|
||||||
imageName: windows-2019
|
|
||||||
pool:
|
|
||||||
vmImage: $(imageName)
|
|
||||||
steps:
|
|
||||||
- task: UsePythonVersion@0
|
|
||||||
inputs:
|
|
||||||
versionSpec: 3.9
|
|
||||||
addToPath: true
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
inputs:
|
|
||||||
artifact: windows-installer
|
|
||||||
path: $(Build.SourcesDirectory)/bin
|
|
||||||
displayName: Retrieve Windows installer
|
|
||||||
- script: |
|
|
||||||
python -m venv venv
|
|
||||||
venv\Scripts\python tools\pip_install.py -e certbot-ci
|
|
||||||
env:
|
|
||||||
PIP_NO_BUILD_ISOLATION: no
|
|
||||||
displayName: Prepare Certbot-CI
|
|
||||||
- script: |
|
|
||||||
set PATH=%ProgramFiles%\Certbot\bin;%PATH%
|
|
||||||
venv\Scripts\python -m pytest certbot-ci\windows_installer_integration_tests --allow-persistent-changes --installer-path $(Build.SourcesDirectory)\bin\certbot-beta-installer-win_amd64.exe
|
|
||||||
displayName: Run windows installer integration tests
|
|
||||||
- script: |
|
|
||||||
set PATH=%ProgramFiles%\Certbot\bin;%PATH%
|
|
||||||
venv\Scripts\python -m pytest certbot-ci\certbot_integration_tests\certbot_tests -n 4
|
|
||||||
displayName: Run certbot integration tests
|
|
||||||
- job: snaps_build
|
- job: snaps_build
|
||||||
pool:
|
pool:
|
||||||
vmImage: ubuntu-22.04
|
vmImage: ubuntu-22.04
|
||||||
|
|||||||
@@ -16,18 +16,6 @@ jobs:
|
|||||||
TOXENV: cover
|
TOXENV: cover
|
||||||
# See explanation under macos-py38-cover.
|
# See explanation under macos-py38-cover.
|
||||||
PIP_USE_PEP517: "true"
|
PIP_USE_PEP517: "true"
|
||||||
windows-py38:
|
|
||||||
IMAGE_NAME: windows-2019
|
|
||||||
PYTHON_VERSION: 3.8
|
|
||||||
TOXENV: py-win
|
|
||||||
windows-py39-cover:
|
|
||||||
IMAGE_NAME: windows-2019
|
|
||||||
PYTHON_VERSION: 3.9
|
|
||||||
TOXENV: cover-win
|
|
||||||
windows-integration-certbot:
|
|
||||||
IMAGE_NAME: windows-2019
|
|
||||||
PYTHON_VERSION: 3.9
|
|
||||||
TOXENV: integration-certbot
|
|
||||||
linux-oldest:
|
linux-oldest:
|
||||||
IMAGE_NAME: ubuntu-22.04
|
IMAGE_NAME: ubuntu-22.04
|
||||||
PYTHON_VERSION: 3.8
|
PYTHON_VERSION: 3.8
|
||||||
@@ -49,7 +37,6 @@ jobs:
|
|||||||
IMAGE_NAME: ubuntu-22.04
|
IMAGE_NAME: ubuntu-22.04
|
||||||
PYTHON_VERSION: 3.8
|
PYTHON_VERSION: 3.8
|
||||||
TOXENV: integration
|
TOXENV: integration
|
||||||
ACME_SERVER: pebble
|
|
||||||
apache-compat:
|
apache-compat:
|
||||||
IMAGE_NAME: ubuntu-22.04
|
IMAGE_NAME: ubuntu-22.04
|
||||||
TOXENV: apache_compat
|
TOXENV: apache_compat
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ SCRIPT_DIRNAME = os.path.dirname(__file__)
|
|||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
with acme_server.ACMEServer('pebble', [], False) as acme_xdist:
|
with acme_server.ACMEServer([], False) as acme_xdist:
|
||||||
environ = os.environ.copy()
|
environ = os.environ.copy()
|
||||||
environ['SERVER'] = acme_xdist['directory_url']
|
environ['SERVER'] = acme_xdist['directory_url']
|
||||||
command = [os.path.join(SCRIPT_DIRNAME, 'apache-conf-test')]
|
command = [os.path.join(SCRIPT_DIRNAME, 'apache-conf-test')]
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
# See https://github.com/letsencrypt/boulder/blob/main/cmd/shell.go for
|
|
||||||
# definitions of these rate limits.
|
|
||||||
certificatesPerName:
|
|
||||||
window: 2160h
|
|
||||||
threshold: 99
|
|
||||||
overrides:
|
|
||||||
ratelimit.me: 1
|
|
||||||
lim.it: 0
|
|
||||||
# Hostnames used by the letsencrypt client integration test.
|
|
||||||
le.wtf: 9999
|
|
||||||
le1.wtf: 9999
|
|
||||||
le2.wtf: 9999
|
|
||||||
le3.wtf: 9999
|
|
||||||
le4.wtf: 9999
|
|
||||||
nginx.wtf: 9999
|
|
||||||
good-caa-reserved.com: 9999
|
|
||||||
bad-caa-reserved.com: 9999
|
|
||||||
ecdsa.le.wtf: 9999
|
|
||||||
must-staple.le.wtf: 9999
|
|
||||||
registrationOverrides:
|
|
||||||
101: 1000
|
|
||||||
registrationsPerIP:
|
|
||||||
window: 168h # 1 week
|
|
||||||
threshold: 9999
|
|
||||||
overrides:
|
|
||||||
127.0.0.1: 999990
|
|
||||||
registrationsPerIPRange:
|
|
||||||
window: 168h # 1 week
|
|
||||||
threshold: 99999
|
|
||||||
overrides:
|
|
||||||
127.0.0.1: 1000000
|
|
||||||
pendingAuthorizationsPerAccount:
|
|
||||||
window: 168h # 1 week, should match pending authorization lifetime.
|
|
||||||
threshold: 999
|
|
||||||
newOrdersPerAccount:
|
|
||||||
window: 3h
|
|
||||||
threshold: 9999
|
|
||||||
certificatesPerFQDNSet:
|
|
||||||
window: 168h
|
|
||||||
threshold: 99999
|
|
||||||
overrides:
|
|
||||||
le.wtf: 9999
|
|
||||||
le1.wtf: 9999
|
|
||||||
le2.wtf: 9999
|
|
||||||
le3.wtf: 9999
|
|
||||||
le.wtf,le1.wtf: 9999
|
|
||||||
good-caa-reserved.com: 9999
|
|
||||||
nginx.wtf: 9999
|
|
||||||
ecdsa.le.wtf: 9999
|
|
||||||
must-staple.le.wtf: 9999
|
|
||||||
certificatesPerFQDNSetFast:
|
|
||||||
window: 2h
|
|
||||||
threshold: 20
|
|
||||||
overrides:
|
|
||||||
le.wtf: 9
|
|
||||||
@@ -23,7 +23,6 @@ class IntegrationTestsContext:
|
|||||||
self.worker_id = 'primary'
|
self.worker_id = 'primary'
|
||||||
acme_xdist = request.config.acme_xdist # type: ignore[attr-defined]
|
acme_xdist = request.config.acme_xdist # type: ignore[attr-defined]
|
||||||
|
|
||||||
self.acme_server = acme_xdist['acme_server']
|
|
||||||
self.directory_url = acme_xdist['directory_url']
|
self.directory_url = acme_xdist['directory_url']
|
||||||
self.tls_alpn_01_port = acme_xdist['https_port'][self.worker_id]
|
self.tls_alpn_01_port = acme_xdist['https_port'][self.worker_id]
|
||||||
self.http_01_port = acme_xdist['http_port'][self.worker_id]
|
self.http_01_port = acme_xdist['http_port'][self.worker_id]
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
from typing import Iterable
|
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
@@ -82,11 +81,9 @@ def test_registration_override(context: IntegrationTestsContext) -> None:
|
|||||||
context.certbot(['update_account', '--email', 'ex1@domain.org,ex2@domain.org'])
|
context.certbot(['update_account', '--email', 'ex1@domain.org,ex2@domain.org'])
|
||||||
stdout2, _ = context.certbot(['show_account'])
|
stdout2, _ = context.certbot(['show_account'])
|
||||||
|
|
||||||
# https://github.com/letsencrypt/boulder/issues/6144
|
assert 'example@domain.org' in stdout1, "New email should be present"
|
||||||
if context.acme_server != 'boulder-v2':
|
assert 'example@domain.org' not in stdout2, "Old email should not be present"
|
||||||
assert 'example@domain.org' in stdout1, "New email should be present"
|
assert 'ex1@domain.org, ex2@domain.org' in stdout2, "New emails should be present"
|
||||||
assert 'example@domain.org' not in stdout2, "Old email should not be present"
|
|
||||||
assert 'ex1@domain.org, ex2@domain.org' in stdout2, "New emails should be present"
|
|
||||||
|
|
||||||
|
|
||||||
def test_prepare_plugins(context: IntegrationTestsContext) -> None:
|
def test_prepare_plugins(context: IntegrationTestsContext) -> None:
|
||||||
@@ -566,19 +563,15 @@ def test_default_rsa_size(context: IntegrationTestsContext) -> None:
|
|||||||
assert_rsa_key(key1, 2048)
|
assert_rsa_key(key1, 2048)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('curve,curve_cls,skip_servers', [
|
@pytest.mark.parametrize('curve,curve_cls', [
|
||||||
# Curve name, Curve class, ACME servers to skip
|
# Curve name, Curve class, ACME servers to skip
|
||||||
('secp256r1', SECP256R1, []),
|
('secp256r1', SECP256R1),
|
||||||
('secp384r1', SECP384R1, []),
|
('secp384r1', SECP384R1),
|
||||||
('secp521r1', SECP521R1, ['boulder-v2'])]
|
('secp521r1', SECP521R1)]
|
||||||
)
|
)
|
||||||
def test_ecdsa_curves(context: IntegrationTestsContext, curve: str, curve_cls: Type[EllipticCurve],
|
def test_ecdsa_curves(context: IntegrationTestsContext, curve: str,
|
||||||
skip_servers: Iterable[str]) -> None:
|
curve_cls: Type[EllipticCurve]) -> None:
|
||||||
"""Test issuance for each supported ECDSA curve"""
|
"""Test issuance for each supported ECDSA curve"""
|
||||||
if context.acme_server in skip_servers:
|
|
||||||
pytest.skip('ACME server {} does not support ECDSA curve {}'
|
|
||||||
.format(context.acme_server, curve))
|
|
||||||
|
|
||||||
domain = context.get_domain('curve')
|
domain = context.get_domain('curve')
|
||||||
context.certbot([
|
context.certbot([
|
||||||
'certonly',
|
'certonly',
|
||||||
@@ -640,9 +633,6 @@ def test_renew_with_ec_keys(context: IntegrationTestsContext) -> None:
|
|||||||
|
|
||||||
def test_ocsp_must_staple(context: IntegrationTestsContext) -> None:
|
def test_ocsp_must_staple(context: IntegrationTestsContext) -> None:
|
||||||
"""Test that OCSP Must-Staple is correctly set in the generated certificate."""
|
"""Test that OCSP Must-Staple is correctly set in the generated certificate."""
|
||||||
if context.acme_server == 'pebble':
|
|
||||||
pytest.skip('Pebble does not support OCSP Must-Staple.')
|
|
||||||
|
|
||||||
certname = context.get_domain('must-staple')
|
certname = context.get_domain('must-staple')
|
||||||
context.certbot(['auth', '--must-staple', '--domains', certname])
|
context.certbot(['auth', '--must-staple', '--domains', certname])
|
||||||
|
|
||||||
@@ -710,17 +700,14 @@ def test_revoke_and_unregister(context: IntegrationTestsContext) -> None:
|
|||||||
assert cert3 in stdout
|
assert cert3 in stdout
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('curve,curve_cls,skip_servers', [
|
@pytest.mark.parametrize('curve,curve_cls', [
|
||||||
('secp256r1', SECP256R1, []),
|
('secp256r1', SECP256R1),
|
||||||
('secp384r1', SECP384R1, []),
|
('secp384r1', SECP384R1),
|
||||||
('secp521r1', SECP521R1, ['boulder-v2'])]
|
('secp521r1', SECP521R1)]
|
||||||
)
|
)
|
||||||
def test_revoke_ecdsa_cert_key(
|
def test_revoke_ecdsa_cert_key(
|
||||||
context: IntegrationTestsContext, curve: str, curve_cls: Type[EllipticCurve],
|
context: IntegrationTestsContext, curve: str, curve_cls: Type[EllipticCurve]) -> None:
|
||||||
skip_servers: Iterable[str]) -> None:
|
|
||||||
"""Test revoking a certificate """
|
"""Test revoking a certificate """
|
||||||
if context.acme_server in skip_servers:
|
|
||||||
pytest.skip(f'ACME server {context.acme_server} does not support ECDSA curve {curve}')
|
|
||||||
cert: str = context.get_domain('curve')
|
cert: str = context.get_domain('curve')
|
||||||
context.certbot([
|
context.certbot([
|
||||||
'certonly',
|
'certonly',
|
||||||
@@ -738,17 +725,14 @@ def test_revoke_ecdsa_cert_key(
|
|||||||
assert stdout.count('INVALID: REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert)
|
assert stdout.count('INVALID: REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('curve,curve_cls,skip_servers', [
|
@pytest.mark.parametrize('curve,curve_cls', [
|
||||||
('secp256r1', SECP256R1, []),
|
('secp256r1', SECP256R1),
|
||||||
('secp384r1', SECP384R1, []),
|
('secp384r1', SECP384R1),
|
||||||
('secp521r1', SECP521R1, ['boulder-v2'])]
|
('secp521r1', SECP521R1)]
|
||||||
)
|
)
|
||||||
def test_revoke_ecdsa_cert_key_delete(
|
def test_revoke_ecdsa_cert_key_delete(
|
||||||
context: IntegrationTestsContext, curve: str, curve_cls: Type[EllipticCurve],
|
context: IntegrationTestsContext, curve: str, curve_cls: Type[EllipticCurve]) -> None:
|
||||||
skip_servers: Iterable[str]) -> None:
|
|
||||||
"""Test revoke and deletion for each supported curve type"""
|
"""Test revoke and deletion for each supported curve type"""
|
||||||
if context.acme_server in skip_servers:
|
|
||||||
pytest.skip(f'ACME server {context.acme_server} does not support ECDSA curve {curve}')
|
|
||||||
cert: str = context.get_domain('curve')
|
cert: str = context.get_domain('curve')
|
||||||
context.certbot([
|
context.certbot([
|
||||||
'certonly',
|
'certonly',
|
||||||
@@ -913,7 +897,7 @@ def test_dry_run_deactivate_authzs(context: IntegrationTestsContext) -> None:
|
|||||||
def test_preferred_chain(context: IntegrationTestsContext) -> None:
|
def test_preferred_chain(context: IntegrationTestsContext) -> None:
|
||||||
"""Test that --preferred-chain results in the correct chain.pem being produced"""
|
"""Test that --preferred-chain results in the correct chain.pem being produced"""
|
||||||
try:
|
try:
|
||||||
issuers = misc.get_acme_issuers(context)
|
issuers = misc.get_acme_issuers()
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
pytest.skip('This ACME server does not support alternative issuers.')
|
pytest.skip('This ACME server does not support alternative issuers.')
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ for a directory a specific configuration using built-in pytest hooks.
|
|||||||
See https://docs.pytest.org/en/latest/reference.html#hook-reference
|
See https://docs.pytest.org/en/latest/reference.html#hook-reference
|
||||||
"""
|
"""
|
||||||
import contextlib
|
import contextlib
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from certbot_integration_tests.utils import acme_server as acme_lib
|
from certbot_integration_tests.utils import acme_server as acme_lib
|
||||||
@@ -20,10 +19,6 @@ def pytest_addoption(parser):
|
|||||||
Standard pytest hook to add options to the pytest parser.
|
Standard pytest hook to add options to the pytest parser.
|
||||||
:param parser: current pytest parser that will be used on the CLI
|
:param parser: current pytest parser that will be used on the CLI
|
||||||
"""
|
"""
|
||||||
parser.addoption('--acme-server', default='pebble',
|
|
||||||
choices=['boulder-v2', 'pebble'],
|
|
||||||
help='select the ACME server to use (boulder-v2, pebble), '
|
|
||||||
'defaulting to pebble')
|
|
||||||
parser.addoption('--dns-server', default='challtestsrv',
|
parser.addoption('--dns-server', default='challtestsrv',
|
||||||
choices=['bind', 'challtestsrv'],
|
choices=['bind', 'challtestsrv'],
|
||||||
help='select the DNS server to use (bind, challtestsrv), '
|
help='select the DNS server to use (bind, challtestsrv), '
|
||||||
@@ -80,23 +75,6 @@ def _setup_primary_node(config):
|
|||||||
|
|
||||||
:param config: Configuration of the pytest primary node. Is modified by this function.
|
:param config: Configuration of the pytest primary node. Is modified by this function.
|
||||||
"""
|
"""
|
||||||
# Check for runtime compatibility: some tools are required to be available in PATH
|
|
||||||
if 'boulder' in config.option.acme_server:
|
|
||||||
try:
|
|
||||||
subprocess.check_output(['docker', '-v'], stderr=subprocess.STDOUT)
|
|
||||||
except (subprocess.CalledProcessError, OSError):
|
|
||||||
raise ValueError('Error: docker is required in PATH to launch the integration tests on'
|
|
||||||
'boulder, but is not installed or not available for current user.')
|
|
||||||
|
|
||||||
try:
|
|
||||||
subprocess.check_output(['docker', 'compose', 'ls'], stderr=subprocess.STDOUT)
|
|
||||||
except (subprocess.CalledProcessError, OSError):
|
|
||||||
raise ValueError(
|
|
||||||
'Error: A version of Docker with the "compose" subcommand '
|
|
||||||
'is required in PATH to launch the integration tests, '
|
|
||||||
'but is not installed or not available for current user.'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parameter numprocesses is added to option by pytest-xdist
|
# Parameter numprocesses is added to option by pytest-xdist
|
||||||
workers = ['primary'] if not config.option.numprocesses\
|
workers = ['primary'] if not config.option.numprocesses\
|
||||||
else ['gw{0}'.format(i) for i in range(config.option.numprocesses)]
|
else ['gw{0}'.format(i) for i in range(config.option.numprocesses)]
|
||||||
@@ -116,8 +94,7 @@ def _setup_primary_node(config):
|
|||||||
|
|
||||||
# By calling setup_acme_server we ensure that all necessary acme server instances will be
|
# By calling setup_acme_server we ensure that all necessary acme server instances will be
|
||||||
# fully started. This runtime is reflected by the acme_xdist returned.
|
# fully started. This runtime is reflected by the acme_xdist returned.
|
||||||
acme_server = acme_lib.ACMEServer(config.option.acme_server, workers,
|
acme_server = acme_lib.ACMEServer(workers, dns_server=acme_dns_server)
|
||||||
dns_server=acme_dns_server)
|
|
||||||
config.add_cleanup(acme_server.stop)
|
config.add_cleanup(acme_server.stop)
|
||||||
print('ACME xdist config:\n{0}'.format(acme_server.acme_xdist))
|
print('ACME xdist config:\n{0}'.format(acme_server.acme_xdist))
|
||||||
acme_server.start()
|
acme_server.start()
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import argparse
|
|||||||
import errno
|
import errno
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from os.path import join
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -18,22 +17,15 @@ from typing import Dict
|
|||||||
from typing import List
|
from typing import List
|
||||||
from typing import Mapping
|
from typing import Mapping
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Tuple
|
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# pylint: disable=wildcard-import,unused-wildcard-import
|
# pylint: disable=wildcard-import,unused-wildcard-import
|
||||||
from certbot_integration_tests.utils import misc
|
from certbot_integration_tests.utils import misc
|
||||||
from certbot_integration_tests.utils import pebble_artifacts
|
from certbot_integration_tests.utils import pebble_artifacts
|
||||||
|
from certbot_integration_tests.utils import pebble_ocsp_server
|
||||||
from certbot_integration_tests.utils import proxy
|
from certbot_integration_tests.utils import proxy
|
||||||
from certbot_integration_tests.utils.constants import *
|
from certbot_integration_tests.utils.constants import *
|
||||||
|
|
||||||
if sys.version_info >= (3, 9): # pragma: no cover
|
|
||||||
import importlib.resources as importlib_resources
|
|
||||||
else: # pragma: no cover
|
|
||||||
import importlib_resources
|
|
||||||
|
|
||||||
|
|
||||||
class ACMEServer:
|
class ACMEServer:
|
||||||
"""
|
"""
|
||||||
@@ -47,34 +39,30 @@ class ACMEServer:
|
|||||||
ACMEServer is also a context manager, and so can be used to ensure ACME server is
|
ACMEServer is also a context manager, and so can be used to ensure ACME server is
|
||||||
started/stopped upon context enter/exit.
|
started/stopped upon context enter/exit.
|
||||||
"""
|
"""
|
||||||
def __init__(self, acme_server: str, nodes: List[str], http_proxy: bool = True,
|
def __init__(self, nodes: List[str], http_proxy: bool = True,
|
||||||
stdout: bool = False, dns_server: Optional[str] = None,
|
stdout: bool = False, dns_server: Optional[str] = None,
|
||||||
http_01_port: Optional[int] = None) -> None:
|
http_01_port: Optional[int] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Create an ACMEServer instance.
|
Create an ACMEServer instance.
|
||||||
:param str acme_server: the type of acme server used (boulder-v2 or pebble)
|
|
||||||
:param list nodes: list of node names that will be setup by pytest xdist
|
:param list nodes: list of node names that will be setup by pytest xdist
|
||||||
:param bool http_proxy: if False do not start the HTTP proxy
|
:param bool http_proxy: if False do not start the HTTP proxy
|
||||||
:param bool stdout: if True stream all subprocesses stdout to standard stdout
|
:param bool stdout: if True stream all subprocesses stdout to standard stdout
|
||||||
:param str dns_server: if set, Pebble/Boulder will use it to resolve domains
|
:param str dns_server: if set, Pebble will use it to resolve domains
|
||||||
:param int http_01_port: port to use for http-01 validation; currently
|
:param int http_01_port: port to use for http-01 validation; currently
|
||||||
only supported for pebble without an HTTP proxy
|
only supported for pebble without an HTTP proxy
|
||||||
"""
|
"""
|
||||||
self._construct_acme_xdist(acme_server, nodes)
|
self._construct_acme_xdist(nodes)
|
||||||
|
|
||||||
self._acme_type = 'pebble' if acme_server == 'pebble' else 'boulder'
|
|
||||||
self._proxy = http_proxy
|
self._proxy = http_proxy
|
||||||
self._workspace = tempfile.mkdtemp()
|
self._workspace = tempfile.mkdtemp()
|
||||||
self._processes: List[subprocess.Popen] = []
|
self._processes: List[subprocess.Popen] = []
|
||||||
self._stdout = sys.stdout if stdout else open(os.devnull, 'w') # pylint: disable=consider-using-with
|
self._stdout = sys.stdout if stdout else open(os.devnull, 'w') # pylint: disable=consider-using-with
|
||||||
self._dns_server = dns_server
|
self._dns_server = dns_server
|
||||||
self._preterminate_cmds_args: List[Tuple[Tuple[Any, ...], Dict[str, Any]]] = []
|
self._http_01_port = DEFAULT_HTTP_01_PORT
|
||||||
self._http_01_port = BOULDER_HTTP_01_PORT if self._acme_type == 'boulder' \
|
|
||||||
else DEFAULT_HTTP_01_PORT
|
|
||||||
if http_01_port:
|
if http_01_port:
|
||||||
if (self._acme_type == 'pebble' and self._proxy) or self._acme_type == 'boulder':
|
if self._proxy:
|
||||||
raise ValueError('Setting http_01_port is not currently supported when '
|
raise ValueError('Setting http_01_port is not currently supported when '
|
||||||
'using Boulder or the HTTP proxy')
|
'using the HTTP proxy')
|
||||||
self._http_01_port = http_01_port
|
self._http_01_port = http_01_port
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
@@ -82,10 +70,7 @@ class ACMEServer:
|
|||||||
try:
|
try:
|
||||||
if self._proxy:
|
if self._proxy:
|
||||||
self._prepare_http_proxy()
|
self._prepare_http_proxy()
|
||||||
if self._acme_type == 'pebble':
|
self._prepare_pebble_server()
|
||||||
self._prepare_pebble_server()
|
|
||||||
if self._acme_type == 'boulder':
|
|
||||||
self._prepare_boulder_server()
|
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
self.stop()
|
self.stop()
|
||||||
raise e
|
raise e
|
||||||
@@ -94,7 +79,6 @@ class ACMEServer:
|
|||||||
"""Stop the test stack, and clean its resources"""
|
"""Stop the test stack, and clean its resources"""
|
||||||
print('=> Tear down the test infrastructure...')
|
print('=> Tear down the test infrastructure...')
|
||||||
try:
|
try:
|
||||||
self._run_preterminate_cmds()
|
|
||||||
for process in self._processes:
|
for process in self._processes:
|
||||||
try:
|
try:
|
||||||
process.terminate()
|
process.terminate()
|
||||||
@@ -120,19 +104,14 @@ class ACMEServer:
|
|||||||
traceback: Optional[TracebackType]) -> None:
|
traceback: Optional[TracebackType]) -> None:
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
def _construct_acme_xdist(self, acme_server: str, nodes: List[str]) -> None:
|
def _construct_acme_xdist(self, nodes: List[str]) -> None:
|
||||||
"""Generate and return the acme_xdist dict"""
|
"""Generate and return the acme_xdist dict"""
|
||||||
acme_xdist: Dict[str, Any] = {'acme_server': acme_server}
|
acme_xdist: Dict[str, Any] = {}
|
||||||
|
|
||||||
# Directory and ACME port are set implicitly in the docker-compose.yml
|
# Directory and ACME port are set implicitly in the docker-compose.yml
|
||||||
# files of Boulder/Pebble.
|
# files of Pebble.
|
||||||
if acme_server == 'pebble':
|
acme_xdist['directory_url'] = PEBBLE_DIRECTORY_URL
|
||||||
acme_xdist['directory_url'] = PEBBLE_DIRECTORY_URL
|
acme_xdist['challtestsrv_url'] = PEBBLE_CHALLTESTSRV_URL
|
||||||
acme_xdist['challtestsrv_url'] = PEBBLE_CHALLTESTSRV_URL
|
|
||||||
else: # boulder
|
|
||||||
acme_xdist['directory_url'] = BOULDER_V2_DIRECTORY_URL
|
|
||||||
acme_xdist['challtestsrv_url'] = BOULDER_V2_CHALLTESTSRV_URL
|
|
||||||
|
|
||||||
acme_xdist['http_port'] = dict(zip(nodes, range(5200, 5200 + len(nodes))))
|
acme_xdist['http_port'] = dict(zip(nodes, range(5200, 5200 + len(nodes))))
|
||||||
acme_xdist['https_port'] = dict(zip(nodes, range(5100, 5100 + len(nodes))))
|
acme_xdist['https_port'] = dict(zip(nodes, range(5100, 5100 + len(nodes))))
|
||||||
acme_xdist['other_port'] = dict(zip(nodes, range(5300, 5300 + len(nodes))))
|
acme_xdist['other_port'] = dict(zip(nodes, range(5300, 5300 + len(nodes))))
|
||||||
@@ -166,11 +145,6 @@ class ACMEServer:
|
|||||||
[pebble_path, '-config', pebble_config_path, '-dnsserver', dns_server, '-strict'],
|
[pebble_path, '-config', pebble_config_path, '-dnsserver', dns_server, '-strict'],
|
||||||
env=environ)
|
env=environ)
|
||||||
|
|
||||||
# pebble_ocsp_server is imported here and not at the top of module in order to avoid a
|
|
||||||
# useless ImportError, in the case where cryptography dependency is too old to support
|
|
||||||
# ocsp, but Boulder is used instead of Pebble, so pebble_ocsp_server is not used. This is
|
|
||||||
# the typical situation of integration-certbot-oldest tox testenv.
|
|
||||||
from certbot_integration_tests.utils import pebble_ocsp_server
|
|
||||||
self._launch_process([sys.executable, pebble_ocsp_server.__file__])
|
self._launch_process([sys.executable, pebble_ocsp_server.__file__])
|
||||||
|
|
||||||
# Wait for the ACME CA server to be up.
|
# Wait for the ACME CA server to be up.
|
||||||
@@ -179,70 +153,6 @@ class ACMEServer:
|
|||||||
|
|
||||||
print('=> Finished pebble instance deployment.')
|
print('=> Finished pebble instance deployment.')
|
||||||
|
|
||||||
def _prepare_boulder_server(self) -> None:
|
|
||||||
"""Configure and launch the Boulder server"""
|
|
||||||
print('=> Starting boulder instance deployment...')
|
|
||||||
instance_path = join(self._workspace, 'boulder')
|
|
||||||
|
|
||||||
# Load Boulder from git, that includes a docker-compose.yml ready for production.
|
|
||||||
process = self._launch_process(['git', 'clone', 'https://github.com/letsencrypt/boulder',
|
|
||||||
'--single-branch', '--depth=1', instance_path])
|
|
||||||
process.wait(MAX_SUBPROCESS_WAIT)
|
|
||||||
|
|
||||||
# Allow Boulder to ignore usual limit rate policies, useful for tests.
|
|
||||||
ref = importlib_resources.files("certbot_integration_tests")
|
|
||||||
ref = ref / "assets" / "boulder-rate-limit-policies.yml"
|
|
||||||
with importlib_resources.as_file(ref) as path:
|
|
||||||
shutil.copyfile(path, join(instance_path, 'test/rate-limit-policies.yml'))
|
|
||||||
|
|
||||||
if self._dns_server:
|
|
||||||
# Change Boulder config to use the provided DNS server
|
|
||||||
for suffix in ["", "-remote-a", "-remote-b"]:
|
|
||||||
with open(join(instance_path, 'test/config/va{}.json'.format(suffix)), 'r') as f:
|
|
||||||
config = json.loads(f.read())
|
|
||||||
config['va']['dnsResolvers'] = [self._dns_server]
|
|
||||||
with open(join(instance_path, 'test/config/va{}.json'.format(suffix)), 'w') as f:
|
|
||||||
f.write(json.dumps(config, indent=2, separators=(',', ': ')))
|
|
||||||
|
|
||||||
# This command needs to be run before we try and terminate running processes because
|
|
||||||
# docker compose up doesn't always respond to SIGTERM. See
|
|
||||||
# https://github.com/certbot/certbot/pull/9435.
|
|
||||||
self._register_preterminate_cmd(['docker', 'compose', 'down'], cwd=instance_path)
|
|
||||||
# Boulder docker generates build artifacts owned by root with 0o744 permissions.
|
|
||||||
# If we started the acme server from a normal user that has access to the Docker
|
|
||||||
# daemon, this user will not be able to delete these artifacts from the host.
|
|
||||||
# We need to do it through a docker.
|
|
||||||
self._register_preterminate_cmd(['docker', 'run', '--rm', '-v',
|
|
||||||
'{0}:/workspace'.format(self._workspace), 'alpine', 'rm',
|
|
||||||
'-rf', '/workspace/boulder'])
|
|
||||||
try:
|
|
||||||
# Launch the Boulder server
|
|
||||||
self._launch_process(['docker', 'compose', 'up', '--force-recreate'], cwd=instance_path)
|
|
||||||
|
|
||||||
# Wait for the ACME CA server to be up.
|
|
||||||
print('=> Waiting for boulder instance to respond...')
|
|
||||||
misc.check_until_timeout(
|
|
||||||
self.acme_xdist['directory_url'], attempts=480)
|
|
||||||
|
|
||||||
if not self._dns_server:
|
|
||||||
# Configure challtestsrv to answer any A record request with ip of the docker host.
|
|
||||||
response = requests.post(
|
|
||||||
f'{BOULDER_V2_CHALLTESTSRV_URL}/set-default-ipv4',
|
|
||||||
json={'ip': '10.77.77.1'},
|
|
||||||
timeout=10
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
except BaseException:
|
|
||||||
# If we failed to set up boulder, print its logs.
|
|
||||||
print('=> Boulder setup failed. Boulder logs are:')
|
|
||||||
process = self._launch_process([
|
|
||||||
'docker', 'compose', 'logs'], cwd=instance_path, force_stderr=True
|
|
||||||
)
|
|
||||||
process.wait(MAX_SUBPROCESS_WAIT)
|
|
||||||
raise
|
|
||||||
|
|
||||||
print('=> Finished boulder instance deployment.')
|
|
||||||
|
|
||||||
def _prepare_http_proxy(self) -> None:
|
def _prepare_http_proxy(self) -> None:
|
||||||
"""Configure and launch an HTTP proxy"""
|
"""Configure and launch an HTTP proxy"""
|
||||||
print(f'=> Configuring the HTTP proxy on port {self._http_01_port}...')
|
print(f'=> Configuring the HTTP proxy on port {self._http_01_port}...')
|
||||||
@@ -267,26 +177,11 @@ class ACMEServer:
|
|||||||
self._processes.append(process)
|
self._processes.append(process)
|
||||||
return process
|
return process
|
||||||
|
|
||||||
def _register_preterminate_cmd(self, *args: Any, **kwargs: Any) -> None:
|
|
||||||
self._preterminate_cmds_args.append((args, kwargs))
|
|
||||||
|
|
||||||
def _run_preterminate_cmds(self) -> None:
|
|
||||||
for args, kwargs in self._preterminate_cmds_args:
|
|
||||||
process = self._launch_process(*args, **kwargs)
|
|
||||||
process.wait(MAX_SUBPROCESS_WAIT)
|
|
||||||
# It's unlikely to matter, but let's clear the list of cleanup commands
|
|
||||||
# once they've been run.
|
|
||||||
self._preterminate_cmds_args.clear()
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
# pylint: disable=missing-function-docstring
|
# pylint: disable=missing-function-docstring
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description='CLI tool to start a local instance of Pebble or Boulder CA server.')
|
description='CLI tool to start a local instance of Pebble CA server.')
|
||||||
parser.add_argument('--server-type', '-s',
|
|
||||||
choices=['pebble', 'boulder-v2'], default='pebble',
|
|
||||||
help='type of CA server to start: can be Pebble or Boulder. '
|
|
||||||
'Pebble is used if not set.')
|
|
||||||
parser.add_argument('--dns-server', '-d',
|
parser.add_argument('--dns-server', '-d',
|
||||||
help='specify the DNS server as `IP:PORT` to use by '
|
help='specify the DNS server as `IP:PORT` to use by '
|
||||||
'Pebble; if not specified, a local mock DNS server will be used to '
|
'Pebble; if not specified, a local mock DNS server will be used to '
|
||||||
@@ -297,8 +192,8 @@ def main() -> None:
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
acme_server = ACMEServer(
|
acme_server = ACMEServer(
|
||||||
args.server_type, [], http_proxy=False, stdout=True,
|
[], http_proxy=False, stdout=True, dns_server=args.dns_server,
|
||||||
dns_server=args.dns_server, http_01_port=args.http_01_port,
|
http_01_port=args.http_01_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
"""Some useful constants to use throughout certbot-ci integration tests"""
|
"""Some useful constants to use throughout certbot-ci integration tests"""
|
||||||
DEFAULT_HTTP_01_PORT = 5002
|
DEFAULT_HTTP_01_PORT = 5002
|
||||||
BOULDER_HTTP_01_PORT = 80
|
|
||||||
TLS_ALPN_01_PORT = 5001
|
TLS_ALPN_01_PORT = 5001
|
||||||
CHALLTESTSRV_PORT = 8055
|
CHALLTESTSRV_PORT = 8055
|
||||||
BOULDER_V2_CHALLTESTSRV_URL = f'http://10.77.77.77:{CHALLTESTSRV_PORT}'
|
|
||||||
BOULDER_V2_DIRECTORY_URL = 'http://localhost:4001/directory'
|
|
||||||
PEBBLE_DIRECTORY_URL = 'https://localhost:14000/dir'
|
PEBBLE_DIRECTORY_URL = 'https://localhost:14000/dir'
|
||||||
PEBBLE_MANAGEMENT_URL = 'https://localhost:15000'
|
PEBBLE_MANAGEMENT_URL = 'https://localhost:15000'
|
||||||
PEBBLE_CHALLTESTSRV_URL = f'http://localhost:{CHALLTESTSRV_PORT}'
|
PEBBLE_CHALLTESTSRV_URL = f'http://localhost:{CHALLTESTSRV_PORT}'
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ from cryptography.x509 import load_pem_x509_certificate
|
|||||||
from OpenSSL import crypto
|
from OpenSSL import crypto
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from certbot_integration_tests.certbot_tests.context import IntegrationTestsContext
|
|
||||||
from certbot_integration_tests.utils.constants import PEBBLE_ALTERNATE_ROOTS
|
from certbot_integration_tests.utils.constants import PEBBLE_ALTERNATE_ROOTS
|
||||||
from certbot_integration_tests.utils.constants import PEBBLE_MANAGEMENT_URL
|
from certbot_integration_tests.utils.constants import PEBBLE_MANAGEMENT_URL
|
||||||
|
|
||||||
@@ -303,16 +302,12 @@ def echo(keyword: str, path: Optional[str] = None) -> str:
|
|||||||
os.path.basename(sys.executable), keyword, ' >> "{0}"'.format(path) if path else '')
|
os.path.basename(sys.executable), keyword, ' >> "{0}"'.format(path) if path else '')
|
||||||
|
|
||||||
|
|
||||||
def get_acme_issuers(context: IntegrationTestsContext) -> List[Certificate]:
|
def get_acme_issuers() -> List[Certificate]:
|
||||||
"""Gets the list of one or more issuer certificates from the ACME server used by the
|
"""Gets the list of one or more issuer certificates from the ACME server used by the
|
||||||
context.
|
context.
|
||||||
:param context: the testing context.
|
:param context: the testing context.
|
||||||
:return: the `list of x509.Certificate` representing the list of issuers.
|
:return: the `list of x509.Certificate` representing the list of issuers.
|
||||||
"""
|
"""
|
||||||
# TODO: in fact, Boulder has alternate chains in config-next/, just not yet in config/.
|
|
||||||
if context.acme_server != "pebble":
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
_suppress_x509_verification_warnings()
|
_suppress_x509_verification_warnings()
|
||||||
|
|
||||||
issuers = []
|
issuers = []
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
# pylint: disable=missing-module-docstring
|
# pylint: disable=missing-module-docstring
|
||||||
import atexit
|
import atexit
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import stat
|
import stat
|
||||||
import sys
|
import sys
|
||||||
|
import zipfile
|
||||||
from contextlib import ExitStack
|
from contextlib import ExitStack
|
||||||
from typing import Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
@@ -17,39 +19,49 @@ if sys.version_info >= (3, 9): # pragma: no cover
|
|||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
import importlib_resources
|
import importlib_resources
|
||||||
|
|
||||||
PEBBLE_VERSION = 'v2.3.1'
|
PEBBLE_VERSION = 'v2.5.1'
|
||||||
|
|
||||||
|
|
||||||
def fetch(workspace: str, http_01_port: int = DEFAULT_HTTP_01_PORT) -> Tuple[str, str, str]:
|
def fetch(workspace: str, http_01_port: int = DEFAULT_HTTP_01_PORT) -> Tuple[str, str, str]:
|
||||||
# pylint: disable=missing-function-docstring
|
# pylint: disable=missing-function-docstring
|
||||||
suffix = 'linux-amd64' if os.name != 'nt' else 'windows-amd64.exe'
|
|
||||||
|
|
||||||
file_manager = ExitStack()
|
file_manager = ExitStack()
|
||||||
atexit.register(file_manager.close)
|
atexit.register(file_manager.close)
|
||||||
pebble_path_ref = importlib_resources.files('certbot_integration_tests') / 'assets'
|
pebble_path_ref = importlib_resources.files('certbot_integration_tests') / 'assets'
|
||||||
assets_path = str(file_manager.enter_context(importlib_resources.as_file(pebble_path_ref)))
|
assets_path = str(file_manager.enter_context(importlib_resources.as_file(pebble_path_ref)))
|
||||||
|
|
||||||
pebble_path = _fetch_asset('pebble', suffix, assets_path)
|
pebble_path = _fetch_asset('pebble', assets_path)
|
||||||
challtestsrv_path = _fetch_asset('pebble-challtestsrv', suffix, assets_path)
|
challtestsrv_path = _fetch_asset('pebble-challtestsrv', assets_path)
|
||||||
pebble_config_path = _build_pebble_config(workspace, http_01_port, assets_path)
|
pebble_config_path = _build_pebble_config(workspace, http_01_port, assets_path)
|
||||||
|
|
||||||
return pebble_path, challtestsrv_path, pebble_config_path
|
return pebble_path, challtestsrv_path, pebble_config_path
|
||||||
|
|
||||||
|
|
||||||
def _fetch_asset(asset: str, suffix: str, assets_path: str) -> str:
|
def _fetch_asset(asset: str, assets_path: str) -> str:
|
||||||
asset_path = os.path.join(assets_path, '{0}_{1}_{2}'.format(asset, PEBBLE_VERSION, suffix))
|
platform = 'linux-amd64'
|
||||||
|
base_url = 'https://github.com/letsencrypt/pebble/releases/download'
|
||||||
|
asset_path = os.path.join(assets_path, f'{asset}_{PEBBLE_VERSION}_{platform}')
|
||||||
if not os.path.exists(asset_path):
|
if not os.path.exists(asset_path):
|
||||||
asset_url = ('https://github.com/letsencrypt/pebble/releases/download/{0}/{1}_{2}'
|
asset_url = f'{base_url}/{PEBBLE_VERSION}/{asset}-{platform}.zip'
|
||||||
.format(PEBBLE_VERSION, asset, suffix))
|
|
||||||
response = requests.get(asset_url, timeout=30)
|
response = requests.get(asset_url, timeout=30)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
asset_data = _unzip_asset(response.content, asset)
|
||||||
|
if asset_data is None:
|
||||||
|
raise ValueError(f"zipfile {asset_url} didn't contain file {asset}")
|
||||||
with open(asset_path, 'wb') as file_h:
|
with open(asset_path, 'wb') as file_h:
|
||||||
file_h.write(response.content)
|
file_h.write(asset_data)
|
||||||
os.chmod(asset_path, os.stat(asset_path).st_mode | stat.S_IEXEC)
|
os.chmod(asset_path, os.stat(asset_path).st_mode | stat.S_IEXEC)
|
||||||
|
|
||||||
return asset_path
|
return asset_path
|
||||||
|
|
||||||
|
|
||||||
|
def _unzip_asset(zipped_data: bytes, asset_name: str) -> Optional[bytes]:
|
||||||
|
with zipfile.ZipFile(io.BytesIO(zipped_data)) as zip_file:
|
||||||
|
for entry in zip_file.filelist:
|
||||||
|
if not entry.is_dir() and entry.filename.endswith(asset_name):
|
||||||
|
return zip_file.read(entry)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _build_pebble_config(workspace: str, http_01_port: int, assets_path: str) -> str:
|
def _build_pebble_config(workspace: str, http_01_port: int, assets_path: str) -> str:
|
||||||
config_path = os.path.join(workspace, 'pebble-config.json')
|
config_path = os.path.join(workspace, 'pebble-config.json')
|
||||||
with open(config_path, 'w') as file_h:
|
with open(config_path, 'w') as file_h:
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
[tox]
|
[tox]
|
||||||
# mypy doesn't current pass for us on Windows. Fixing that is being tracked by
|
envlist = {cover,lint}-posix,mypy
|
||||||
# https://github.com/certbot/certbot/issues/7803.
|
|
||||||
envlist = {cover,lint}-{win,posix},mypy
|
|
||||||
|
|
||||||
skipsdist = true
|
skipsdist = true
|
||||||
|
|
||||||
@@ -10,11 +8,10 @@ pytest = python -m pytest {posargs}
|
|||||||
# Paths are listed on one line because tox seems to have inconsistent
|
# Paths are listed on one line because tox seems to have inconsistent
|
||||||
# behavior with substitutions that contain line continuations, see
|
# behavior with substitutions that contain line continuations, see
|
||||||
# https://github.com/tox-dev/tox/issues/2069 for more info.
|
# https://github.com/tox-dev/tox/issues/2069 for more info.
|
||||||
source_paths = acme/acme certbot/certbot certbot-apache/certbot_apache certbot-ci/certbot_integration_tests certbot-ci/snap_integration_tests certbot-ci/windows_installer_integration_tests certbot-compatibility-test/certbot_compatibility_test certbot-dns-cloudflare/certbot_dns_cloudflare certbot-dns-digitalocean/certbot_dns_digitalocean certbot-dns-dnsimple/certbot_dns_dnsimple certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy certbot-dns-gehirn/certbot_dns_gehirn certbot-dns-google/certbot_dns_google certbot-dns-linode/certbot_dns_linode certbot-dns-luadns/certbot_dns_luadns certbot-dns-nsone/certbot_dns_nsone certbot-dns-ovh/certbot_dns_ovh certbot-dns-rfc2136/certbot_dns_rfc2136 certbot-dns-route53/certbot_dns_route53 certbot-dns-sakuracloud/certbot_dns_sakuracloud certbot-nginx/certbot_nginx
|
source_paths = acme/acme certbot/certbot certbot-apache/certbot_apache certbot-ci/certbot_integration_tests certbot-ci/snap_integration_tests certbot-compatibility-test/certbot_compatibility_test certbot-dns-cloudflare/certbot_dns_cloudflare certbot-dns-digitalocean/certbot_dns_digitalocean certbot-dns-dnsimple/certbot_dns_dnsimple certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy certbot-dns-gehirn/certbot_dns_gehirn certbot-dns-google/certbot_dns_google certbot-dns-linode/certbot_dns_linode certbot-dns-luadns/certbot_dns_luadns certbot-dns-nsone/certbot_dns_nsone certbot-dns-ovh/certbot_dns_ovh certbot-dns-rfc2136/certbot_dns_rfc2136 certbot-dns-route53/certbot_dns_route53 certbot-dns-sakuracloud/certbot_dns_sakuracloud certbot-nginx/certbot_nginx
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
platform =
|
platform =
|
||||||
win: win32
|
|
||||||
posix: ^(?!.*win32).*$
|
posix: ^(?!.*win32).*$
|
||||||
setenv =
|
setenv =
|
||||||
PYTEST_ADDOPTS = {env:PYTEST_ADDOPTS:--numprocesses auto}
|
PYTEST_ADDOPTS = {env:PYTEST_ADDOPTS:--numprocesses auto}
|
||||||
@@ -25,7 +22,7 @@ deps =
|
|||||||
-e acme[test]
|
-e acme[test]
|
||||||
-e certbot
|
-e certbot
|
||||||
-e certbot[test]
|
-e certbot[test]
|
||||||
!win: -e certbot-apache[dev]
|
-e certbot-apache[dev]
|
||||||
-e certbot-dns-cloudflare
|
-e certbot-dns-cloudflare
|
||||||
-e certbot-dns-digitalocean
|
-e certbot-dns-digitalocean
|
||||||
-e certbot-dns-dnsimple
|
-e certbot-dns-dnsimple
|
||||||
@@ -49,14 +46,9 @@ commands =
|
|||||||
echo "Unrecognized environment name {envname}"
|
echo "Unrecognized environment name {envname}"
|
||||||
false
|
false
|
||||||
|
|
||||||
[testenv:py-win]
|
|
||||||
commands =
|
|
||||||
{[base]pytest} acme certbot certbot-dns-cloudflare certbot-dns-digitalocean certbot-dns-dnsimple certbot-dns-dnsmadeeasy certbot-dns-gehirn certbot-dns-google certbot-dns-linode certbot-dns-luadns certbot-dns-nsone certbot-dns-ovh certbot-dns-rfc2136 certbot-dns-route53 certbot-dns-sakuracloud certbot-nginx
|
|
||||||
|
|
||||||
[testenv:py{,-posix}]
|
[testenv:py{,-posix}]
|
||||||
# We want to test everything we do on Windows plus the Apache plugin.
|
|
||||||
commands =
|
commands =
|
||||||
{[testenv:py-win]commands} certbot-apache
|
{[base]pytest} acme certbot certbot-dns-cloudflare certbot-dns-digitalocean certbot-dns-dnsimple certbot-dns-dnsmadeeasy certbot-dns-gehirn certbot-dns-google certbot-dns-linode certbot-dns-luadns certbot-dns-nsone certbot-dns-ovh certbot-dns-rfc2136 certbot-dns-route53 certbot-dns-sakuracloud certbot-nginx certbot-apache
|
||||||
|
|
||||||
[testenv:py3{,8,9,10,11,12}]
|
[testenv:py3{,8,9,10,11,12}]
|
||||||
commands = {[testenv:py]commands}
|
commands = {[testenv:py]commands}
|
||||||
@@ -77,7 +69,6 @@ commands = {[testenv:py]commands}
|
|||||||
|
|
||||||
[testenv:cover]
|
[testenv:cover]
|
||||||
coverage_report = python -m coverage report
|
coverage_report = python -m coverage report
|
||||||
# These coverage report commands are used on both posix and windows
|
|
||||||
common_coverage_report_commands =
|
common_coverage_report_commands =
|
||||||
{[testenv:cover]coverage_report} --fail-under 99 --include certbot-dns-cloudflare/*
|
{[testenv:cover]coverage_report} --fail-under 99 --include certbot-dns-cloudflare/*
|
||||||
{[testenv:cover]coverage_report} --fail-under 99 --include certbot-dns-digitalocean/*
|
{[testenv:cover]coverage_report} --fail-under 99 --include certbot-dns-digitalocean/*
|
||||||
@@ -104,14 +95,7 @@ commands =
|
|||||||
[testenv:cover-posix]
|
[testenv:cover-posix]
|
||||||
commands = {[testenv:cover]commands}
|
commands = {[testenv:cover]commands}
|
||||||
|
|
||||||
[testenv:cover-win]
|
[testenv:lint{,-posix}]
|
||||||
commands =
|
|
||||||
{[testenv:py-win]commands} --cov --cov-report=
|
|
||||||
{[testenv:cover]coverage_report} --fail-under 99 --include acme/*
|
|
||||||
{[testenv:cover]coverage_report} --fail-under 96 --include certbot/*
|
|
||||||
{[testenv:cover]common_coverage_report_commands}
|
|
||||||
|
|
||||||
[testenv:lint{,-win,-posix}]
|
|
||||||
commands = python -m pylint --reports=n --rcfile=.pylintrc {[base]source_paths}
|
commands = python -m pylint --reports=n --rcfile=.pylintrc {[base]source_paths}
|
||||||
|
|
||||||
[testenv:mypy]
|
[testenv:mypy]
|
||||||
@@ -241,7 +225,6 @@ deps =
|
|||||||
-e certbot-ci
|
-e certbot-ci
|
||||||
commands =
|
commands =
|
||||||
{[base]pytest} certbot-ci/certbot_integration_tests \
|
{[base]pytest} certbot-ci/certbot_integration_tests \
|
||||||
--acme-server={env:ACME_SERVER:pebble} \
|
|
||||||
--cov=acme --cov=certbot --cov=certbot_nginx --cov-report= \
|
--cov=acme --cov=certbot --cov=certbot_nginx --cov-report= \
|
||||||
--cov-config=certbot-ci/certbot_integration_tests/.coveragerc
|
--cov-config=certbot-ci/certbot_integration_tests/.coveragerc
|
||||||
coverage report --include 'certbot/*' --show-missing --fail-under=65
|
coverage report --include 'certbot/*' --show-missing --fail-under=65
|
||||||
@@ -255,7 +238,6 @@ deps =
|
|||||||
-e certbot-ci
|
-e certbot-ci
|
||||||
commands =
|
commands =
|
||||||
{[base]pytest} certbot-ci/certbot_integration_tests/certbot_tests \
|
{[base]pytest} certbot-ci/certbot_integration_tests/certbot_tests \
|
||||||
--acme-server={env:ACME_SERVER:pebble} \
|
|
||||||
--cov=acme --cov=certbot --cov-report= \
|
--cov=acme --cov=certbot --cov-report= \
|
||||||
--cov-config=certbot-ci/certbot_integration_tests/.coveragerc
|
--cov-config=certbot-ci/certbot_integration_tests/.coveragerc
|
||||||
coverage report --include 'certbot/*' --show-missing --fail-under=62
|
coverage report --include 'certbot/*' --show-missing --fail-under=62
|
||||||
@@ -268,7 +250,7 @@ deps =
|
|||||||
-e certbot-ci
|
-e certbot-ci
|
||||||
commands =
|
commands =
|
||||||
{[base]pytest} certbot-ci/certbot_integration_tests/rfc2136_tests \
|
{[base]pytest} certbot-ci/certbot_integration_tests/rfc2136_tests \
|
||||||
--acme-server=pebble --dns-server=bind \
|
--dns-server=bind \
|
||||||
--numprocesses=1 \
|
--numprocesses=1 \
|
||||||
--cov=acme --cov=certbot --cov=certbot_dns_rfc2136 --cov-report= \
|
--cov=acme --cov=certbot --cov=certbot_dns_rfc2136 --cov-report= \
|
||||||
--cov-config=certbot-ci/certbot_integration_tests/.coveragerc
|
--cov-config=certbot-ci/certbot_integration_tests/.coveragerc
|
||||||
@@ -280,8 +262,7 @@ description = Run integration tests with Certbot outside of the tox virtual envi
|
|||||||
deps =
|
deps =
|
||||||
-e certbot-ci
|
-e certbot-ci
|
||||||
commands =
|
commands =
|
||||||
{[base]pytest} certbot-ci/certbot_integration_tests \
|
{[base]pytest} certbot-ci/certbot_integration_tests
|
||||||
--acme-server={env:ACME_SERVER:pebble}
|
|
||||||
passenv = DOCKER_*
|
passenv = DOCKER_*
|
||||||
|
|
||||||
[testenv:integration-certbot-oldest]
|
[testenv:integration-certbot-oldest]
|
||||||
@@ -292,8 +273,7 @@ deps =
|
|||||||
basepython =
|
basepython =
|
||||||
{[testenv:oldest]basepython}
|
{[testenv:oldest]basepython}
|
||||||
commands =
|
commands =
|
||||||
{[base]pytest} certbot-ci/certbot_integration_tests/certbot_tests \
|
{[base]pytest} certbot-ci/certbot_integration_tests/certbot_tests
|
||||||
--acme-server={env:ACME_SERVER:pebble}
|
|
||||||
passenv = DOCKER_*
|
passenv = DOCKER_*
|
||||||
setenv = {[testenv:oldest]setenv}
|
setenv = {[testenv:oldest]setenv}
|
||||||
|
|
||||||
@@ -306,8 +286,7 @@ deps =
|
|||||||
basepython =
|
basepython =
|
||||||
{[testenv:oldest]basepython}
|
{[testenv:oldest]basepython}
|
||||||
commands =
|
commands =
|
||||||
{[base]pytest} certbot-ci/certbot_integration_tests/nginx_tests \
|
{[base]pytest} certbot-ci/certbot_integration_tests/nginx_tests
|
||||||
--acme-server={env:ACME_SERVER:pebble}
|
|
||||||
passenv = DOCKER_*
|
passenv = DOCKER_*
|
||||||
setenv = {[testenv:oldest]setenv}
|
setenv = {[testenv:oldest]setenv}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user