certlord 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- certlord-1.0.0/.github/scripts/check-release.py +32 -0
- certlord-1.0.0/.github/scripts/check-test-collection.py +138 -0
- certlord-1.0.0/.github/scripts/install-lifecycle-tools.sh +24 -0
- certlord-1.0.0/.github/tests/test_collection_guard.py +68 -0
- certlord-1.0.0/.github/workflows/debian.yml +158 -0
- certlord-1.0.0/.github/workflows/documentation.yml +33 -0
- certlord-1.0.0/.github/workflows/lifecycle.yml +39 -0
- certlord-1.0.0/.github/workflows/pypi.yml +98 -0
- certlord-1.0.0/.github/workflows/tests.yml +56 -0
- certlord-1.0.0/.github/workflows/textual.yml +20 -0
- certlord-1.0.0/AGENTS.md +66 -0
- certlord-1.0.0/CHANGELOG +25 -0
- certlord-1.0.0/CONTRIBUTING.md +8 -0
- certlord-1.0.0/LICENSE +674 -0
- certlord-1.0.0/MANIFEST.in +34 -0
- certlord-1.0.0/MIGRATION.md +201 -0
- certlord-1.0.0/PKG-INFO +130 -0
- certlord-1.0.0/README.md +80 -0
- certlord-1.0.0/RELEASE +1 -0
- certlord-1.0.0/VERSION +1 -0
- certlord-1.0.0/_templates/documentation-tracks.html +6 -0
- certlord-1.0.0/assets/brand/README.md +37 -0
- certlord-1.0.0/assets/brand/favicon/android-chrome-192x192.png +0 -0
- certlord-1.0.0/assets/brand/favicon/android-chrome-512x512.png +0 -0
- certlord-1.0.0/assets/brand/favicon/apple-touch-icon.png +0 -0
- certlord-1.0.0/assets/brand/favicon/favicon-16x16.png +0 -0
- certlord-1.0.0/assets/brand/favicon/favicon-32x32.png +0 -0
- certlord-1.0.0/assets/brand/favicon/favicon-48x48.png +0 -0
- certlord-1.0.0/assets/brand/favicon/favicon.ico +0 -0
- certlord-1.0.0/assets/brand/favicon/favicon.svg +9 -0
- certlord-1.0.0/assets/brand/favicon/site.webmanifest +19 -0
- certlord-1.0.0/assets/brand/png/icon-1024.png +0 -0
- certlord-1.0.0/assets/brand/png/icon-128.png +0 -0
- certlord-1.0.0/assets/brand/png/icon-256.png +0 -0
- certlord-1.0.0/assets/brand/png/icon-512.png +0 -0
- certlord-1.0.0/assets/brand/png/icon-dark-512.png +0 -0
- certlord-1.0.0/assets/brand/png/icon-mono-black-512.png +0 -0
- certlord-1.0.0/assets/brand/png/icon-mono-white-512.png +0 -0
- certlord-1.0.0/assets/brand/png/logo-horizontal-1200.png +0 -0
- certlord-1.0.0/assets/brand/png/logo-horizontal-dark-1200.png +0 -0
- certlord-1.0.0/assets/brand/png/logo-horizontal-mono-black-1200.png +0 -0
- certlord-1.0.0/assets/brand/png/logo-horizontal-mono-white-1200.png +0 -0
- certlord-1.0.0/assets/brand/png/logo-stacked-1200.png +0 -0
- certlord-1.0.0/assets/brand/png/logo-stacked-dark-1200.png +0 -0
- certlord-1.0.0/assets/brand/png/logo-stacked-mono-black-1200.png +0 -0
- certlord-1.0.0/assets/brand/png/logo-stacked-mono-white-1200.png +0 -0
- certlord-1.0.0/assets/brand/png/wordmark-1200.png +0 -0
- certlord-1.0.0/assets/brand/png/wordmark-dark-1200.png +0 -0
- certlord-1.0.0/assets/brand/png/wordmark-mono-black-1200.png +0 -0
- certlord-1.0.0/assets/brand/png/wordmark-mono-white-1200.png +0 -0
- certlord-1.0.0/assets/brand/svg/icon-dark.svg +9 -0
- certlord-1.0.0/assets/brand/svg/icon-mono-black.svg +9 -0
- certlord-1.0.0/assets/brand/svg/icon-mono-white.svg +9 -0
- certlord-1.0.0/assets/brand/svg/icon.svg +9 -0
- certlord-1.0.0/assets/brand/svg/logo-horizontal-dark.svg +17 -0
- certlord-1.0.0/assets/brand/svg/logo-horizontal-mono-black.svg +17 -0
- certlord-1.0.0/assets/brand/svg/logo-horizontal-mono-white.svg +17 -0
- certlord-1.0.0/assets/brand/svg/logo-horizontal.svg +17 -0
- certlord-1.0.0/assets/brand/svg/logo-stacked-dark.svg +17 -0
- certlord-1.0.0/assets/brand/svg/logo-stacked-mono-black.svg +17 -0
- certlord-1.0.0/assets/brand/svg/logo-stacked-mono-white.svg +17 -0
- certlord-1.0.0/assets/brand/svg/logo-stacked.svg +17 -0
- certlord-1.0.0/assets/brand/svg/wordmark-dark.svg +9 -0
- certlord-1.0.0/assets/brand/svg/wordmark-mono-black.svg +9 -0
- certlord-1.0.0/assets/brand/svg/wordmark-mono-white.svg +9 -0
- certlord-1.0.0/assets/brand/svg/wordmark.svg +9 -0
- certlord-1.0.0/bin/certlord +165 -0
- certlord-1.0.0/certlord/__init__.py +4 -0
- certlord-1.0.0/certlord/adapters/__init__.py +1 -0
- certlord-1.0.0/certlord/adapters/api_auth.py +21 -0
- certlord-1.0.0/certlord/adapters/auton_receipt.py +71 -0
- certlord-1.0.0/certlord/adapters/commands.py +122 -0
- certlord-1.0.0/certlord/adapters/cycle_progress.py +77 -0
- certlord-1.0.0/certlord/adapters/deployment_lease.py +51 -0
- certlord-1.0.0/certlord/adapters/deployment_queue.py +40 -0
- certlord-1.0.0/certlord/adapters/dns_policy.py +120 -0
- certlord-1.0.0/certlord/adapters/issuance_command.py +38 -0
- certlord-1.0.0/certlord/adapters/operations.py +69 -0
- certlord-1.0.0/certlord/adapters/pending.py +99 -0
- certlord-1.0.0/certlord/adapters/processes.py +75 -0
- certlord-1.0.0/certlord/adapters/redis_client.py +38 -0
- certlord-1.0.0/certlord/adapters/tls_probe.py +42 -0
- certlord-1.0.0/certlord/adapters/tls_verification.py +91 -0
- certlord-1.0.0/certlord/adapters/vault_auth.py +43 -0
- certlord-1.0.0/certlord/adapters/vault_store.py +123 -0
- certlord-1.0.0/certlord/classes/__init__.py +28 -0
- certlord-1.0.0/certlord/classes/certbot_handler.py +166 -0
- certlord-1.0.0/certlord/classes/config.py +73 -0
- certlord-1.0.0/certlord/classes/deployer.py +179 -0
- certlord-1.0.0/certlord/classes/exceptions.py +19 -0
- certlord-1.0.0/certlord/classes/ssl_cert_auto_object.py +79 -0
- certlord-1.0.0/certlord/classes/ssl_checker.py +374 -0
- certlord-1.0.0/certlord/classes/vault_crawler.py +192 -0
- certlord-1.0.0/certlord/client/__init__.py +1 -0
- certlord-1.0.0/certlord/client/api.py +71 -0
- certlord-1.0.0/certlord/client/cli.py +105 -0
- certlord-1.0.0/certlord/client/textual_tui.py +52 -0
- certlord-1.0.0/certlord/client/tui.py +53 -0
- certlord-1.0.0/certlord/composition.py +83 -0
- certlord-1.0.0/certlord/configuration_schema.py +61 -0
- certlord-1.0.0/certlord/modules/__init__.py +28 -0
- certlord-1.0.0/certlord/modules/letsencrypt.py +157 -0
- certlord-1.0.0/certlord/modules/route_auth.py +24 -0
- certlord-1.0.0/certlord/modules/ssl_certs.py +216 -0
- certlord-1.0.0/certlord/ports/__init__.py +1 -0
- certlord-1.0.0/certlord/ports/certificates.py +51 -0
- certlord-1.0.0/certlord/services/__init__.py +1 -0
- certlord-1.0.0/certlord/services/api_access.py +18 -0
- certlord-1.0.0/certlord/services/certificate_ids.py +40 -0
- certlord-1.0.0/certlord/services/certificates.py +367 -0
- certlord-1.0.0/certlord/services/cycle_progress.py +69 -0
- certlord-1.0.0/certlord/services/identity_definition.py +32 -0
- certlord-1.0.0/certlord/services/import_material.py +89 -0
- certlord-1.0.0/certlord/services/issuance.py +7 -0
- certlord-1.0.0/certlord/services/monitoring.py +75 -0
- certlord-1.0.0/certlord/services/observations.py +141 -0
- certlord-1.0.0/certlord/services/operation_tracking.py +86 -0
- certlord-1.0.0/certlord/services/runtime.py +260 -0
- certlord-1.0.0/certlord/services/verification.py +25 -0
- certlord-1.0.0/certlord.egg-info/PKG-INFO +130 -0
- certlord-1.0.0/certlord.egg-info/SOURCES.txt +230 -0
- certlord-1.0.0/certlord.egg-info/dependency_links.txt +1 -0
- certlord-1.0.0/certlord.egg-info/requires.txt +17 -0
- certlord-1.0.0/certlord.egg-info/top_level.txt +1 -0
- certlord-1.0.0/conf.py +22 -0
- certlord-1.0.0/constraints-minimum.txt +15 -0
- certlord-1.0.0/debian/changelog +25 -0
- certlord-1.0.0/debian/compat +1 -0
- certlord-1.0.0/debian/control +15 -0
- certlord-1.0.0/debian/copyright +28 -0
- certlord-1.0.0/debian/default +3 -0
- certlord-1.0.0/debian/dirs +2 -0
- certlord-1.0.0/debian/docs +1 -0
- certlord-1.0.0/debian/init +65 -0
- certlord-1.0.0/debian/install +3 -0
- certlord-1.0.0/debian/links +1 -0
- certlord-1.0.0/debian/logrotate +10 -0
- certlord-1.0.0/debian/postinst +21 -0
- certlord-1.0.0/debian/requirements.txt +4 -0
- certlord-1.0.0/debian/rules +25 -0
- certlord-1.0.0/debian/service +19 -0
- certlord-1.0.0/docs/acme-connector.md +117 -0
- certlord-1.0.0/docs/architecture.md +421 -0
- certlord-1.0.0/docs/certificate-identity.md +104 -0
- certlord-1.0.0/docs/certificate-import.md +139 -0
- certlord-1.0.0/docs/components.md +57 -0
- certlord-1.0.0/docs/configuration-validation.md +34 -0
- certlord-1.0.0/docs/contributing.md +95 -0
- certlord-1.0.0/docs/contributors.rst +14 -0
- certlord-1.0.0/docs/debian-installation.md +140 -0
- certlord-1.0.0/docs/images/capture.json +7 -0
- certlord-1.0.0/docs/images/cli-certificates.png +0 -0
- certlord-1.0.0/docs/images/cli-certificates.txt +7 -0
- certlord-1.0.0/docs/images/textual-details.png +0 -0
- certlord-1.0.0/docs/images/textual-inventory.png +0 -0
- certlord-1.0.0/docs/images/tui-details.png +0 -0
- certlord-1.0.0/docs/images/tui-details.txt +12 -0
- certlord-1.0.0/docs/images/tui-inventory.png +0 -0
- certlord-1.0.0/docs/images/tui-inventory.txt +4 -0
- certlord-1.0.0/docs/operation-correlation.md +97 -0
- certlord-1.0.0/docs/operations.md +226 -0
- certlord-1.0.0/docs/rc-evaluation.md +90 -0
- certlord-1.0.0/docs/release-notes.md +43 -0
- certlord-1.0.0/docs/requirements.txt +4 -0
- certlord-1.0.0/docs/screenshots.md +41 -0
- certlord-1.0.0/docs/supervision.md +92 -0
- certlord-1.0.0/docs/testing.md +58 -0
- certlord-1.0.0/docs/textual.md +23 -0
- certlord-1.0.0/docs/tls-verification.md +90 -0
- certlord-1.0.0/docs/vault-approle.md +63 -0
- certlord-1.0.0/etc/certlord/certbot-httpreq.yml.example +21 -0
- certlord-1.0.0/etc/certlord/certlord.yml +180 -0
- certlord-1.0.0/etc/certlord/credentials.yml.example +15 -0
- certlord-1.0.0/etc/certlord/vault-policy.hcl.example +19 -0
- certlord-1.0.0/etc/sudoers.d/90-certlord +2 -0
- certlord-1.0.0/index.rst +36 -0
- certlord-1.0.0/pyproject.toml +3 -0
- certlord-1.0.0/requirements.txt +14 -0
- certlord-1.0.0/scripts/build_brand_assets.py +36 -0
- certlord-1.0.0/scripts/capture_docs.py +170 -0
- certlord-1.0.0/scripts/capture_textual.py +75 -0
- certlord-1.0.0/scripts/requirements-docs.txt +3 -0
- certlord-1.0.0/setup.cfg +7 -0
- certlord-1.0.0/setup.py +48 -0
- certlord-1.0.0/setup.yml +24 -0
- certlord-1.0.0/setup.yml.j2 +24 -0
- certlord-1.0.0/tests/contracts/test_components.py +174 -0
- certlord-1.0.0/tests/import_fixture.py +28 -0
- certlord-1.0.0/tests/integration/auton-routes.yml +43 -0
- certlord-1.0.0/tests/integration/lifecycle.py +2128 -0
- certlord-1.0.0/tests/integration/network_faults.py +163 -0
- certlord-1.0.0/tests/integration/systemd.Dockerfile +17 -0
- certlord-1.0.0/tests/integration/systemd_acceptance.py +171 -0
- certlord-1.0.0/tests/integration/systemd_process.py +68 -0
- certlord-1.0.0/tests/integration/vault-fixture.service +5 -0
- certlord-1.0.0/tests/test_authenticated_routes.py +36 -0
- certlord-1.0.0/tests/test_auton_receipt.py +118 -0
- certlord-1.0.0/tests/test_backend_timeouts.py +160 -0
- certlord-1.0.0/tests/test_boundaries.py +113 -0
- certlord-1.0.0/tests/test_certificate_ids.py +96 -0
- certlord-1.0.0/tests/test_certificate_import.py +137 -0
- certlord-1.0.0/tests/test_certificate_replacement.py +117 -0
- certlord-1.0.0/tests/test_certificates.py +93 -0
- certlord-1.0.0/tests/test_challenge_expiry.py +41 -0
- certlord-1.0.0/tests/test_cli.py +90 -0
- certlord-1.0.0/tests/test_commands.py +41 -0
- certlord-1.0.0/tests/test_configuration_schema.py +64 -0
- certlord-1.0.0/tests/test_crawler_retry.py +60 -0
- certlord-1.0.0/tests/test_cycle_progress.py +128 -0
- certlord-1.0.0/tests/test_daemon_permissions.py +48 -0
- certlord-1.0.0/tests/test_deployer.py +86 -0
- certlord-1.0.0/tests/test_deployment_coordination.py +93 -0
- certlord-1.0.0/tests/test_deployment_queue.py +40 -0
- certlord-1.0.0/tests/test_http_errors.py +83 -0
- certlord-1.0.0/tests/test_identity_fingerprint.py +133 -0
- certlord-1.0.0/tests/test_issuance.py +81 -0
- certlord-1.0.0/tests/test_monitoring_deletion.py +44 -0
- certlord-1.0.0/tests/test_monitoring_startup.py +61 -0
- certlord-1.0.0/tests/test_monitoring_transport.py +76 -0
- certlord-1.0.0/tests/test_observations.py +147 -0
- certlord-1.0.0/tests/test_operation_tracking.py +132 -0
- certlord-1.0.0/tests/test_operations.py +147 -0
- certlord-1.0.0/tests/test_processes.py +93 -0
- certlord-1.0.0/tests/test_removal_coordination.py +88 -0
- certlord-1.0.0/tests/test_reverse_index.py +26 -0
- certlord-1.0.0/tests/test_save_coordination.py +72 -0
- certlord-1.0.0/tests/test_tls_probe_transport.py +81 -0
- certlord-1.0.0/tests/test_tls_verification.py +129 -0
- certlord-1.0.0/tests/test_vault_removal.py +52 -0
- certlord-1.0.0/tests/test_worker_log_safety.py +79 -0
- certlord-1.0.0/textual_tests/test_browser.py +28 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Reject mismatched versions, tags or private documents before publication."""
|
|
2
|
+
import ast
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path, PurePosixPath
|
|
5
|
+
import tarfile
|
|
6
|
+
import zipfile
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
root = Path(__file__).resolve().parents[2]
|
|
10
|
+
version = (root / 'VERSION').read_text().strip()
|
|
11
|
+
config = yaml.safe_load((root / 'setup.yml').read_text())
|
|
12
|
+
assert config['version'] == config['release'] == version
|
|
13
|
+
assert (root / 'RELEASE').read_text().strip() == version
|
|
14
|
+
module = ast.parse((root / 'bin/certlord').read_text())
|
|
15
|
+
cli_version = next(ast.literal_eval(n.value) for n in module.body
|
|
16
|
+
if isinstance(n, ast.Assign) and any(isinstance(t, ast.Name) and t.id == '__version__' for t in n.targets))
|
|
17
|
+
assert cli_version == version
|
|
18
|
+
ref = os.environ.get('GITHUB_REF', '')
|
|
19
|
+
if ref.startswith('refs/tags/'):
|
|
20
|
+
assert ref == 'refs/tags/v' + version, (ref, version)
|
|
21
|
+
files = sorted((root / 'dist').iterdir())
|
|
22
|
+
assert len(files) == 2 and sum(p.suffix == '.whl' for p in files) == 1
|
|
23
|
+
assert sum(p.name.endswith('.tar.gz') for p in files) == 1
|
|
24
|
+
for path in files:
|
|
25
|
+
assert version in path.name, path.name
|
|
26
|
+
names = zipfile.ZipFile(path).namelist() if path.suffix == '.whl' else tarfile.open(path).getnames()
|
|
27
|
+
for name in names:
|
|
28
|
+
parts = PurePosixPath(name).parts
|
|
29
|
+
assert not name.startswith('/') and '..' not in parts, name
|
|
30
|
+
assert not any(part in {'.git', 'ROADMAP.md', 'SANITIZED-MANIFEST.json', 'pr-history.json', 'evidence', 'framework_candidates'} for part in parts), name
|
|
31
|
+
assert not any(part.startswith(('review-20', 'release-readiness-', 'http-boundary-review-', 'operation-audit-review-')) for part in parts), name
|
|
32
|
+
print('Release metadata, tag and distribution inventory verified:', version)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Compare test declarations with actual collection (Python 3.8+ CI helper).
|
|
3
|
+
|
|
4
|
+
Run before the normal test command, with the same interpreter and environment.
|
|
5
|
+
Pass every separately discovered directory together; skipped tests still count
|
|
6
|
+
as collected. This does not execute tests or replace the normal test command.
|
|
7
|
+
"""
|
|
8
|
+
import argparse
|
|
9
|
+
import ast
|
|
10
|
+
import inspect
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import sys
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
TEST_PATTERN = 'test*.py'
|
|
17
|
+
FUNCTION_NODES = (ast.FunctionDef, ast.AsyncFunctionDef)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def declarations(directories):
|
|
21
|
+
expected = set()
|
|
22
|
+
files = {path.resolve() for directory in directories
|
|
23
|
+
for path in directory.rglob(TEST_PATTERN)}
|
|
24
|
+
for path in sorted(files):
|
|
25
|
+
tree = ast.parse(path.read_bytes(), filename=str(path))
|
|
26
|
+
|
|
27
|
+
def visit(nodes, prefix=''):
|
|
28
|
+
for node in nodes:
|
|
29
|
+
if isinstance(node, FUNCTION_NODES):
|
|
30
|
+
if node.name.startswith('test'):
|
|
31
|
+
key = (path, prefix + node.name)
|
|
32
|
+
if key in expected:
|
|
33
|
+
raise ValueError('Duplicate test declaration: %s:%s' % key)
|
|
34
|
+
expected.add(key)
|
|
35
|
+
# Helpers defined inside a test are not separate tests.
|
|
36
|
+
elif isinstance(node, ast.ClassDef):
|
|
37
|
+
visit(node.body, prefix + node.name + '.')
|
|
38
|
+
else:
|
|
39
|
+
# Include declarations behind module/class-level conditions.
|
|
40
|
+
visit(list(ast.iter_child_nodes(node)), prefix)
|
|
41
|
+
|
|
42
|
+
visit(tree.body)
|
|
43
|
+
return expected
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def identity(obj):
|
|
47
|
+
obj = inspect.unwrap(obj)
|
|
48
|
+
source = inspect.getsourcefile(obj)
|
|
49
|
+
return (Path(source).resolve(), obj.__qualname__) if source else None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def unittest_collection(directories):
|
|
53
|
+
collected = set()
|
|
54
|
+
count = 0
|
|
55
|
+
|
|
56
|
+
def cases(suite):
|
|
57
|
+
for item in suite:
|
|
58
|
+
if isinstance(item, unittest.TestSuite):
|
|
59
|
+
yield from cases(item)
|
|
60
|
+
else:
|
|
61
|
+
yield item
|
|
62
|
+
|
|
63
|
+
for directory in directories:
|
|
64
|
+
loader = unittest.TestLoader()
|
|
65
|
+
suite = loader.discover(str(directory), pattern=TEST_PATTERN)
|
|
66
|
+
if loader.errors:
|
|
67
|
+
raise ValueError('\n'.join(loader.errors))
|
|
68
|
+
items = list(cases(suite))
|
|
69
|
+
if not items:
|
|
70
|
+
raise ValueError('Empty test suite: %s' % directory)
|
|
71
|
+
count += len(items)
|
|
72
|
+
for case in items:
|
|
73
|
+
method = getattr(case, case._testMethodName)
|
|
74
|
+
collected.add(identity(method))
|
|
75
|
+
return collected, count
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def pytest_collection(directories):
|
|
79
|
+
import pytest
|
|
80
|
+
|
|
81
|
+
class Collector:
|
|
82
|
+
def __init__(self):
|
|
83
|
+
self.collected = set()
|
|
84
|
+
self.counts = {directory: 0 for directory in directories}
|
|
85
|
+
self.count = 0
|
|
86
|
+
|
|
87
|
+
def pytest_collection_finish(self, session):
|
|
88
|
+
self.count = len(session.items)
|
|
89
|
+
for item in session.items:
|
|
90
|
+
self.collected.add(identity(item.obj))
|
|
91
|
+
for directory in directories:
|
|
92
|
+
if directory in Path(str(item.path)).resolve().parents:
|
|
93
|
+
self.counts[directory] += 1
|
|
94
|
+
|
|
95
|
+
collector = Collector()
|
|
96
|
+
status = pytest.main(['--collect-only', '-q'] +
|
|
97
|
+
[str(directory) for directory in directories],
|
|
98
|
+
plugins=[collector])
|
|
99
|
+
if status != 0:
|
|
100
|
+
raise ValueError('pytest collection failed with exit code %s' % status)
|
|
101
|
+
for directory, count in collector.counts.items():
|
|
102
|
+
if not count:
|
|
103
|
+
raise ValueError('Empty test suite: %s' % directory)
|
|
104
|
+
return collector.collected, collector.count
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def main():
|
|
108
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
109
|
+
parser.add_argument('--runner', choices=('unittest', 'pytest'), required=True)
|
|
110
|
+
parser.add_argument('directories', type=Path, nargs='+')
|
|
111
|
+
args = parser.parse_args()
|
|
112
|
+
# Match python -m unittest/pytest: use the working directory, never the
|
|
113
|
+
# script repository. Installed-package jobs run from outside the checkout.
|
|
114
|
+
sys.path.insert(0, str(Path.cwd()))
|
|
115
|
+
directories = [directory.resolve() for directory in args.directories]
|
|
116
|
+
try:
|
|
117
|
+
for directory in directories:
|
|
118
|
+
if not directory.is_dir():
|
|
119
|
+
raise ValueError('Missing test directory: %s' % directory)
|
|
120
|
+
expected = declarations(directories)
|
|
121
|
+
collect = pytest_collection if args.runner == 'pytest' else unittest_collection
|
|
122
|
+
collected, count = collect(directories)
|
|
123
|
+
missing = expected - collected
|
|
124
|
+
if missing:
|
|
125
|
+
raise ValueError('Tests not collected by %s:\n%s' % (
|
|
126
|
+
args.runner, '\n'.join('%s:%s' % key for key in sorted(missing))))
|
|
127
|
+
if not expected:
|
|
128
|
+
raise ValueError('No test declarations found')
|
|
129
|
+
print('Collection verified: %d declarations, %d cases (%s)' %
|
|
130
|
+
(len(expected), count, args.runner))
|
|
131
|
+
except (ImportError, OSError, SyntaxError, ValueError) as exc:
|
|
132
|
+
print(str(exc), file=sys.stderr)
|
|
133
|
+
return 1
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == '__main__':
|
|
138
|
+
sys.exit(main())
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Disposable Linux amd64 integration tools; no installation into the host PATH.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
fixture_dir="${1:?Usage: install-lifecycle-tools.sh DIRECTORY}"
|
|
5
|
+
mkdir -p "$fixture_dir"
|
|
6
|
+
cd "$fixture_dir"
|
|
7
|
+
for binary in pebble pebble-challtestsrv; do
|
|
8
|
+
curl --fail --silent --show-error --location --retry 2 --max-time 120 \
|
|
9
|
+
"https://github.com/letsencrypt/pebble/releases/download/v2.10.1/${binary}-linux-amd64.tar.gz" \
|
|
10
|
+
--output "$binary.tar.gz"
|
|
11
|
+
done
|
|
12
|
+
curl --fail --silent --show-error --location --retry 2 --max-time 120 \
|
|
13
|
+
https://releases.hashicorp.com/vault/1.20.4/vault_1.20.4_linux_amd64.zip --output vault.zip
|
|
14
|
+
sha256sum --check <<'SUMS'
|
|
15
|
+
4f2fcb5bca8c85c9cf73ad140fccfc0d2be40bd81ab99879c79b7b8a0b4f70ed pebble.tar.gz
|
|
16
|
+
e93a5aa25ecdf3af2f9fbb2de32b0173e64a2eae81002a4ccfe35fa6f4f60b92 pebble-challtestsrv.tar.gz
|
|
17
|
+
fc5fb5d01d192f1216b139fb5c6af17e3af742aaeffc289fd861920ec55f2c9c vault.zip
|
|
18
|
+
SUMS
|
|
19
|
+
for binary in pebble pebble-challtestsrv; do
|
|
20
|
+
tar --no-same-owner -xzf "$binary.tar.gz"
|
|
21
|
+
cp "$binary-linux-amd64/linux/amd64/$binary" "$binary"
|
|
22
|
+
chmod +x "$binary"
|
|
23
|
+
done
|
|
24
|
+
unzip -q vault.zip vault
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Regression tests for false-green collection failures."""
|
|
2
|
+
import importlib.util
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
import unittest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
GUARD = Path(__file__).resolve().parents[1] / 'scripts' / 'check-test-collection.py'
|
|
11
|
+
CASE = 'import unittest\nclass Example(unittest.TestCase):\n def test_ok(self): pass\n'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CollectionGuardTests(unittest.TestCase):
|
|
15
|
+
def check(self, files, directories=('tests',), runner='unittest', succeeds=False):
|
|
16
|
+
with tempfile.TemporaryDirectory() as temporary:
|
|
17
|
+
root = Path(temporary)
|
|
18
|
+
for directory in directories:
|
|
19
|
+
(root / directory).mkdir(parents=True, exist_ok=True)
|
|
20
|
+
for name, content in files.items():
|
|
21
|
+
path = root / name
|
|
22
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
path.write_text(content)
|
|
24
|
+
result = subprocess.run(
|
|
25
|
+
[sys.executable, str(GUARD), '--runner', runner] + list(directories),
|
|
26
|
+
cwd=str(root), stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
27
|
+
universal_newlines=True, timeout=30)
|
|
28
|
+
self.assertEqual(result.returncode == 0, succeeds, result.stdout)
|
|
29
|
+
return result.stdout
|
|
30
|
+
|
|
31
|
+
def test_valid_and_skipped_cases_are_collected(self):
|
|
32
|
+
self.check({'tests/test_valid.py': CASE +
|
|
33
|
+
' @unittest.skip("requires integration service")\n'
|
|
34
|
+
' def test_skipped(self): pass\n'}, succeeds=True)
|
|
35
|
+
|
|
36
|
+
def test_unittest_rejects_ignored_function_next_to_valid_case(self):
|
|
37
|
+
output = self.check({'tests/test_mixed.py': CASE + '\ndef test_lost(): pass\n'})
|
|
38
|
+
self.assertIn('test_lost', output)
|
|
39
|
+
|
|
40
|
+
def test_empty_suite_fails(self):
|
|
41
|
+
self.assertIn('Empty test suite', self.check({}))
|
|
42
|
+
|
|
43
|
+
def test_nested_directory_requires_separate_discovery(self):
|
|
44
|
+
files = {'tests/test_outer.py': CASE, 'tests/contracts/test_inner.py': CASE}
|
|
45
|
+
self.assertIn('not collected', self.check(files))
|
|
46
|
+
self.check(files, directories=('tests', 'tests/contracts'), succeeds=True)
|
|
47
|
+
|
|
48
|
+
def test_import_error_fails(self):
|
|
49
|
+
self.assertIn('ImportError', self.check({
|
|
50
|
+
'tests/test_broken.py': 'raise ImportError("broken dependency")\n'}))
|
|
51
|
+
|
|
52
|
+
def test_duplicate_definition_fails(self):
|
|
53
|
+
self.assertIn('Duplicate test declaration', self.check({
|
|
54
|
+
'tests/test_duplicate.py': CASE + ' def test_ok(self): pass\n'}))
|
|
55
|
+
|
|
56
|
+
def test_plain_class_is_not_a_unittest_case(self):
|
|
57
|
+
self.assertIn('TestLost.test_lost', self.check({
|
|
58
|
+
'tests/test_plain.py': CASE + '\nclass TestLost:\n def test_lost(self): pass\n'}))
|
|
59
|
+
|
|
60
|
+
@unittest.skipUnless(importlib.util.find_spec('pytest'), 'pytest runner only')
|
|
61
|
+
def test_pytest_parameterization_and_deselection(self):
|
|
62
|
+
files = {'tests/test_parameters.py':
|
|
63
|
+
'import pytest\n@pytest.mark.parametrize("value", [1, 2])\n'
|
|
64
|
+
'def test_value(value): assert value > 0\n'}
|
|
65
|
+
self.check(files, runner='pytest', succeeds=True)
|
|
66
|
+
files['tests/conftest.py'] = (
|
|
67
|
+
'def pytest_collection_modifyitems(items):\n items.clear()\n')
|
|
68
|
+
self.check(files, runner='pytest')
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
name: Debian packaging
|
|
2
|
+
on:
|
|
3
|
+
workflow_call:
|
|
4
|
+
pull_request:
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
jobs:
|
|
9
|
+
debian12:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
timeout-minutes: 20
|
|
12
|
+
container: debian:12
|
|
13
|
+
steps:
|
|
14
|
+
- name: Install build tools
|
|
15
|
+
run: |
|
|
16
|
+
apt-get update -qq
|
|
17
|
+
apt-get install -y --no-install-recommends ca-certificates git build-essential debhelper dh-virtualenv python3 python3-venv python3-dev python3-setuptools python3-yaml libcurl4-openssl-dev libssl-dev
|
|
18
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
|
19
|
+
- name: Build Debian package with isolated dependencies
|
|
20
|
+
run: |
|
|
21
|
+
dpkg-buildpackage -us -uc -b
|
|
22
|
+
mkdir -p deb-artifacts
|
|
23
|
+
cp ../certlord_*.deb deb-artifacts/
|
|
24
|
+
- name: Install without network access to package indexes
|
|
25
|
+
run: |
|
|
26
|
+
export PIP_NO_INDEX=1
|
|
27
|
+
export http_proxy=http://127.0.0.1:9
|
|
28
|
+
export https_proxy=http://127.0.0.1:9
|
|
29
|
+
dpkg -i ../certlord_*.deb
|
|
30
|
+
/opt/venvs/certlord/bin/python -X faulthandler -c 'import sys; print(sys.version, flush=True); import pycurl; print("pycurl imported", flush=True); import cryptography; print("cryptography imported", flush=True)'
|
|
31
|
+
PYTHONFAULTHANDLER=1 /usr/bin/certlord --help
|
|
32
|
+
/opt/venvs/certlord/bin/python -m pip check
|
|
33
|
+
/usr/bin/python3 -I -c 'import importlib.util; assert importlib.util.find_spec("certlord") is None'
|
|
34
|
+
test ! -e /etc/certlord/certlord.yml
|
|
35
|
+
test ! -e /etc/systemd/system/multi-user.target.wants/certlord.service
|
|
36
|
+
test ! -e /etc/sudoers.d/90-certlord
|
|
37
|
+
test "$(stat -c %U /opt/venvs/certlord)" = root
|
|
38
|
+
test "$(stat -c %U /var/lib/certlord)" = certlord
|
|
39
|
+
- name: Installed regression suites outside source checkout
|
|
40
|
+
run: |
|
|
41
|
+
suite_dir=$(mktemp -d)
|
|
42
|
+
cp -R tests etc "$suite_dir/"
|
|
43
|
+
cp .github/scripts/check-test-collection.py "$suite_dir/"
|
|
44
|
+
cd "$suite_dir"
|
|
45
|
+
/opt/venvs/certlord/bin/python check-test-collection.py --runner unittest tests tests/contracts
|
|
46
|
+
/opt/venvs/certlord/bin/python -m unittest discover -s tests -q
|
|
47
|
+
/opt/venvs/certlord/bin/python -m unittest discover -s tests/contracts -q
|
|
48
|
+
- name: Reconfigure and remove without deleting local state
|
|
49
|
+
run: |
|
|
50
|
+
touch /var/lib/certlord/retained-fixture
|
|
51
|
+
PIP_NO_INDEX=1 dpkg -i ../certlord_*.deb
|
|
52
|
+
dpkg --remove certlord
|
|
53
|
+
test -f /var/lib/certlord/retained-fixture
|
|
54
|
+
test ! -e /usr/bin/certlord
|
|
55
|
+
- uses: actions/upload-artifact@v4
|
|
56
|
+
with:
|
|
57
|
+
name: certlord-debian12-amd64
|
|
58
|
+
path: deb-artifacts/*.deb
|
|
59
|
+
if-no-files-found: error
|
|
60
|
+
retention-days: 7
|
|
61
|
+
clean-runtime:
|
|
62
|
+
needs: debian12
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
timeout-minutes: 10
|
|
65
|
+
container: debian:12
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/download-artifact@v4
|
|
68
|
+
with:
|
|
69
|
+
name: certlord-debian12-amd64
|
|
70
|
+
path: /tmp/certlord-package
|
|
71
|
+
- name: Install on clean runtime without build dependencies
|
|
72
|
+
run: |
|
|
73
|
+
apt-get update -qq
|
|
74
|
+
apt-get install -y --no-install-recommends /tmp/certlord-package/certlord_*.deb
|
|
75
|
+
cd /tmp
|
|
76
|
+
PYTHONFAULTHANDLER=1 /usr/bin/certlord --help
|
|
77
|
+
/opt/venvs/certlord/bin/certbot --version
|
|
78
|
+
/opt/venvs/certlord/bin/auton --help
|
|
79
|
+
/opt/venvs/certlord/bin/python -m pip check
|
|
80
|
+
/usr/bin/python3 -I -c 'import importlib.util; assert importlib.util.find_spec("certlord") is None'
|
|
81
|
+
test ! -e /etc/certlord/certlord.yml
|
|
82
|
+
test ! -e /etc/sudoers.d/90-certlord
|
|
83
|
+
test ! -e /etc/systemd/system/multi-user.target.wants/certlord.service
|
|
84
|
+
systemd-runtime:
|
|
85
|
+
needs: debian12
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
timeout-minutes: 12
|
|
88
|
+
steps:
|
|
89
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
|
90
|
+
- uses: actions/download-artifact@v4
|
|
91
|
+
with:
|
|
92
|
+
name: certlord-debian12-amd64
|
|
93
|
+
path: tests/integration/package
|
|
94
|
+
- name: Fetch checksum-verified disposable Vault
|
|
95
|
+
run: |
|
|
96
|
+
curl --fail --silent --show-error --location --retry 2 --max-time 120 https://releases.hashicorp.com/vault/1.20.4/vault_1.20.4_linux_amd64.zip --output "$RUNNER_TEMP/vault.zip"
|
|
97
|
+
echo "fc5fb5d01d192f1216b139fb5c6af17e3af742aaeffc289fd861920ec55f2c9c $RUNNER_TEMP/vault.zip" | sha256sum --check
|
|
98
|
+
unzip -q "$RUNNER_TEMP/vault.zip" vault -d tests/integration
|
|
99
|
+
- name: Build disposable systemd acceptance image
|
|
100
|
+
run: docker build --target service -f tests/integration/systemd.Dockerfile -t certlord-systemd-fixture tests/integration
|
|
101
|
+
- name: Boot systemd and verify installed service
|
|
102
|
+
run: |
|
|
103
|
+
mkdir -p "$RUNNER_TEMP/systemd-evidence"
|
|
104
|
+
docker run -d --name certlord-systemd --privileged --cgroupns=private --tmpfs /run --tmpfs /run/lock --mount "type=bind,src=$RUNNER_TEMP/systemd-evidence,dst=/evidence" certlord-systemd-fixture
|
|
105
|
+
timeout 45 bash -c 'until docker exec certlord-systemd systemctl list-units --no-pager >/dev/null 2>&1; do sleep 1; done'
|
|
106
|
+
docker exec certlord-systemd /opt/venvs/certlord/bin/python /fixture/systemd_acceptance.py
|
|
107
|
+
- name: Retain sanitized systemd evidence
|
|
108
|
+
if: always()
|
|
109
|
+
uses: actions/upload-artifact@v4
|
|
110
|
+
with:
|
|
111
|
+
name: certlord-systemd-${{ github.run_attempt }}
|
|
112
|
+
path: ${{ runner.temp }}/systemd-evidence/systemd.json
|
|
113
|
+
if-no-files-found: warn
|
|
114
|
+
retention-days: 7
|
|
115
|
+
- name: Remove disposable acceptance container
|
|
116
|
+
if: always()
|
|
117
|
+
run: |
|
|
118
|
+
if docker container inspect certlord-systemd >/dev/null 2>&1; then
|
|
119
|
+
docker rm -f certlord-systemd
|
|
120
|
+
fi
|
|
121
|
+
systemd-lifecycle:
|
|
122
|
+
needs: debian12
|
|
123
|
+
runs-on: ubuntu-latest
|
|
124
|
+
timeout-minutes: 15
|
|
125
|
+
steps:
|
|
126
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
|
127
|
+
- uses: actions/download-artifact@v4
|
|
128
|
+
with:
|
|
129
|
+
name: certlord-debian12-amd64
|
|
130
|
+
path: tests/integration/package
|
|
131
|
+
- name: Prepare checksum-verified disposable tools and examples
|
|
132
|
+
run: |
|
|
133
|
+
bash .github/scripts/install-lifecycle-tools.sh "$GITHUB_WORKSPACE/tests/integration/tools"
|
|
134
|
+
cp tests/integration/tools/vault tests/integration/vault
|
|
135
|
+
mkdir tests/integration/source
|
|
136
|
+
cp -R etc tests/integration/source/
|
|
137
|
+
- name: Build installed-service lifecycle image
|
|
138
|
+
run: docker build --target lifecycle -f tests/integration/systemd.Dockerfile -t certlord-systemd-lifecycle tests/integration
|
|
139
|
+
- name: Interrupt actual issuance and deployment through systemctl
|
|
140
|
+
run: |
|
|
141
|
+
mkdir -p "$RUNNER_TEMP/systemd-lifecycle-evidence"
|
|
142
|
+
docker run -d --name certlord-systemd-lifecycle --privileged --cgroupns=private --tmpfs /run --tmpfs /run/lock --mount "type=bind,src=$RUNNER_TEMP/systemd-lifecycle-evidence,dst=/evidence" certlord-systemd-lifecycle
|
|
143
|
+
timeout 45 bash -c 'until docker exec certlord-systemd-lifecycle systemctl list-units --no-pager >/dev/null 2>&1; do sleep 1; done'
|
|
144
|
+
docker exec -e CERTLORD_DISPOSABLE_SYSTEMD=1 -e PATH=/opt/venvs/certlord/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin certlord-systemd-lifecycle /opt/venvs/certlord/bin/python /fixture/lifecycle.py run --systemd-scheduler --tools /fixture/tools --source /fixture/source --report /evidence/report.json
|
|
145
|
+
- name: Retain sanitized systemd lifecycle evidence
|
|
146
|
+
if: always()
|
|
147
|
+
uses: actions/upload-artifact@v4
|
|
148
|
+
with:
|
|
149
|
+
name: certlord-systemd-lifecycle-${{ github.run_attempt }}
|
|
150
|
+
path: ${{ runner.temp }}/systemd-lifecycle-evidence/report.json
|
|
151
|
+
if-no-files-found: warn
|
|
152
|
+
retention-days: 7
|
|
153
|
+
- name: Remove disposable lifecycle container
|
|
154
|
+
if: always()
|
|
155
|
+
run: |
|
|
156
|
+
if docker container inspect certlord-systemd-lifecycle >/dev/null 2>&1; then
|
|
157
|
+
docker rm -f certlord-systemd-lifecycle
|
|
158
|
+
fi
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Documentation captures
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
pull_request:
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
jobs:
|
|
9
|
+
captures:
|
|
10
|
+
runs-on: ubuntu-24.04
|
|
11
|
+
timeout-minutes: 10
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
|
14
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
|
|
15
|
+
with:
|
|
16
|
+
python-version: '3.12'
|
|
17
|
+
- name: Install package and capture tools
|
|
18
|
+
run: |
|
|
19
|
+
sudo apt-get update -qq
|
|
20
|
+
sudo apt-get install -y fonts-dejavu-core
|
|
21
|
+
python -m pip install . -r scripts/requirements-docs.txt
|
|
22
|
+
python -m pip check
|
|
23
|
+
- name: Capture installed client and check documented output
|
|
24
|
+
run: |
|
|
25
|
+
python scripts/capture_docs.py --installed --output /tmp/certlord-captures
|
|
26
|
+
for file in docs/images/*.txt docs/images/capture.json; do
|
|
27
|
+
diff -u "$file" "/tmp/certlord-captures/$(basename "$file")"
|
|
28
|
+
done
|
|
29
|
+
- uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: certlord-documentation-captures
|
|
32
|
+
path: /tmp/certlord-captures
|
|
33
|
+
if-no-files-found: error
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: lifecycle acceptance
|
|
2
|
+
on:
|
|
3
|
+
workflow_call:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
jobs:
|
|
11
|
+
lifecycle:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
timeout-minutes: 15
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
|
16
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
|
|
17
|
+
with:
|
|
18
|
+
python-version: '3.12'
|
|
19
|
+
- name: Install runtime and pinned external clients
|
|
20
|
+
run: |
|
|
21
|
+
sudo apt-get update -qq
|
|
22
|
+
sudo apt-get install -y redis-server unzip
|
|
23
|
+
python -m pip install . 'certbot-httpreq==0.0.24' 'acme-http-connector==0.2.1' 'certbot==5.8.0' 'auton==1.2.1' 'autond==1.2.1'
|
|
24
|
+
- name: Install checksum-verified disposable CA and Vault
|
|
25
|
+
run: bash .github/scripts/install-lifecycle-tools.sh "$RUNNER_TEMP/certlord-tools"
|
|
26
|
+
- name: Installed lifecycle, actual served TLS and failure recovery
|
|
27
|
+
working-directory: ${{ runner.temp }}
|
|
28
|
+
run: >-
|
|
29
|
+
python "$GITHUB_WORKSPACE/tests/integration/lifecycle.py" run
|
|
30
|
+
--tools "$RUNNER_TEMP/certlord-tools" --source "$GITHUB_WORKSPACE"
|
|
31
|
+
--report "$RUNNER_TEMP/certlord-evidence/report.json"
|
|
32
|
+
- name: Retain only sanitized test evidence
|
|
33
|
+
if: always()
|
|
34
|
+
uses: actions/upload-artifact@v4
|
|
35
|
+
with:
|
|
36
|
+
name: certlord-lifecycle-${{ github.run_attempt }}
|
|
37
|
+
path: ${{ runner.temp }}/certlord-evidence/report.json
|
|
38
|
+
if-no-files-found: warn
|
|
39
|
+
retention-days: 7
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
name: Release candidate and PyPI publication
|
|
2
|
+
on:
|
|
3
|
+
workflow_dispatch:
|
|
4
|
+
push:
|
|
5
|
+
tags: ['v*']
|
|
6
|
+
pull_request:
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
concurrency:
|
|
10
|
+
group: publish-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: false
|
|
12
|
+
jobs:
|
|
13
|
+
tests:
|
|
14
|
+
uses: ./.github/workflows/tests.yml
|
|
15
|
+
lifecycle:
|
|
16
|
+
uses: ./.github/workflows/lifecycle.yml
|
|
17
|
+
debian:
|
|
18
|
+
uses: ./.github/workflows/debian.yml
|
|
19
|
+
build:
|
|
20
|
+
needs: [tests, lifecycle, debian]
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
timeout-minutes: 15
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
|
25
|
+
with:
|
|
26
|
+
persist-credentials: false
|
|
27
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
|
|
28
|
+
with:
|
|
29
|
+
python-version: '3.12'
|
|
30
|
+
- run: python -m pip install build twine PyYAML
|
|
31
|
+
- run: python -m build
|
|
32
|
+
- run: python -m twine check --strict dist/*
|
|
33
|
+
- run: python .github/scripts/check-release.py
|
|
34
|
+
- uses: actions/upload-artifact@v4
|
|
35
|
+
with:
|
|
36
|
+
name: release-distributions
|
|
37
|
+
path: dist/
|
|
38
|
+
if-no-files-found: error
|
|
39
|
+
tag:
|
|
40
|
+
needs: build
|
|
41
|
+
if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') && github.repository == 'decryptus/certlord' && !github.event.repository.private
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
permissions:
|
|
44
|
+
contents: write
|
|
45
|
+
outputs:
|
|
46
|
+
version: ${{ steps.tag.outputs.version }}
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
|
49
|
+
with:
|
|
50
|
+
persist-credentials: false
|
|
51
|
+
- id: tag
|
|
52
|
+
env:
|
|
53
|
+
GH_TOKEN: ${{ github.token }}
|
|
54
|
+
run: |
|
|
55
|
+
set -eu
|
|
56
|
+
version=$(cat VERSION)
|
|
57
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
58
|
+
tag="v$version"
|
|
59
|
+
if existing=$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$tag" --jq '.object.sha'); then
|
|
60
|
+
test "$existing" = "$GITHUB_SHA"
|
|
61
|
+
else
|
|
62
|
+
gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" -f ref="refs/tags/$tag" -f sha="$GITHUB_SHA"
|
|
63
|
+
fi
|
|
64
|
+
publish:
|
|
65
|
+
needs: [build, tag]
|
|
66
|
+
if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') && github.repository == 'decryptus/certlord' && !github.event.repository.private
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
environment: pypi
|
|
69
|
+
permissions:
|
|
70
|
+
id-token: write
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/download-artifact@v4
|
|
73
|
+
with:
|
|
74
|
+
name: release-distributions
|
|
75
|
+
path: dist/
|
|
76
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
77
|
+
github-release:
|
|
78
|
+
needs: [publish, tag]
|
|
79
|
+
runs-on: ubuntu-latest
|
|
80
|
+
permissions:
|
|
81
|
+
contents: write
|
|
82
|
+
steps:
|
|
83
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
|
84
|
+
with:
|
|
85
|
+
persist-credentials: false
|
|
86
|
+
- uses: actions/download-artifact@v4
|
|
87
|
+
with:
|
|
88
|
+
name: release-distributions
|
|
89
|
+
path: dist/
|
|
90
|
+
- name: Publish matching GitHub release
|
|
91
|
+
env:
|
|
92
|
+
GH_TOKEN: ${{ github.token }}
|
|
93
|
+
RELEASE_TAG: v${{ needs.tag.outputs.version }}
|
|
94
|
+
run: |
|
|
95
|
+
set -eu
|
|
96
|
+
prerelease_flag=''
|
|
97
|
+
case "$RELEASE_TAG" in *rc*|*a[0-9]*|*b[0-9]*) prerelease_flag='--prerelease';; esac
|
|
98
|
+
gh release create "$RELEASE_TAG" dist/* --verify-tag --title "CertLord $RELEASE_TAG" --notes-file docs/release-notes.md $prerelease_flag
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
on:
|
|
3
|
+
workflow_call:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
jobs:
|
|
9
|
+
tests:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python: ['3.11', '3.12']
|
|
14
|
+
dependencies: ['minimum', 'latest']
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
|
17
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python }}
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: |
|
|
22
|
+
if [ "${{ matrix.dependencies }}" = "minimum" ]; then
|
|
23
|
+
python -m pip install -r requirements.txt -c constraints-minimum.txt build
|
|
24
|
+
else
|
|
25
|
+
python -m pip install -r requirements.txt build
|
|
26
|
+
fi
|
|
27
|
+
python -m pip check
|
|
28
|
+
- name: Verify collection and its failure guards
|
|
29
|
+
run: |
|
|
30
|
+
python .github/scripts/check-test-collection.py --runner unittest tests tests/contracts
|
|
31
|
+
python .github/scripts/check-test-collection.py --runner unittest .github/tests
|
|
32
|
+
python -m unittest discover -s .github/tests -v
|
|
33
|
+
- name: Unit tests
|
|
34
|
+
run: python -m unittest discover -s tests -v
|
|
35
|
+
- name: Component contracts
|
|
36
|
+
run: python -m unittest discover -s tests/contracts -v
|
|
37
|
+
- name: Build source archive and wheel
|
|
38
|
+
run: python -m build
|
|
39
|
+
- name: Install built wheel
|
|
40
|
+
run: |
|
|
41
|
+
python -m pip install --force-reinstall --no-deps dist/*.whl
|
|
42
|
+
python -m pip check
|
|
43
|
+
- name: Installed CLI smoke test
|
|
44
|
+
working-directory: /tmp
|
|
45
|
+
run: certlord --help
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
- name: Installed-package regression suites outside checkout
|
|
49
|
+
working-directory: /tmp
|
|
50
|
+
run: |
|
|
51
|
+
suite_dir=$(mktemp -d)
|
|
52
|
+
cp -R "$GITHUB_WORKSPACE/tests" "$GITHUB_WORKSPACE/etc" "$suite_dir/"
|
|
53
|
+
cd "$suite_dir"
|
|
54
|
+
python "$GITHUB_WORKSPACE/.github/scripts/check-test-collection.py" --runner unittest tests tests/contracts
|
|
55
|
+
python -m unittest discover -s tests -v
|
|
56
|
+
python -m unittest discover -s tests/contracts -v
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Textual terminal tests
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
permissions:
|
|
4
|
+
contents: read
|
|
5
|
+
jobs:
|
|
6
|
+
textual:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
steps:
|
|
9
|
+
- uses: actions/checkout@v4
|
|
10
|
+
- uses: actions/setup-python@v5
|
|
11
|
+
with:
|
|
12
|
+
python-version: '3.12'
|
|
13
|
+
- run: python -m pip install '.[textual]' resvg-py
|
|
14
|
+
- run: python .github/scripts/check-test-collection.py --runner unittest textual_tests
|
|
15
|
+
- run: python -m unittest discover -s textual_tests -v
|
|
16
|
+
- run: python scripts/capture_textual.py --output /tmp/textual-captures --png
|
|
17
|
+
- uses: actions/upload-artifact@v4
|
|
18
|
+
with:
|
|
19
|
+
name: textual-captures
|
|
20
|
+
path: /tmp/textual-captures
|