Skip to content

Switch TLS certificates to APP mode and share via peer relation#461

Draft
Copilot wants to merge 7 commits intomainfrom
copilot/share-single-certificate-ha-deployment
Draft

Switch TLS certificates to APP mode and share via peer relation#461
Copilot wants to merge 7 commits intomainfrom
copilot/share-single-certificate-ha-deployment

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 13, 2026

In HA deployments, each unit was requesting its own TLS certificate (UNIT mode), hitting Let's Encrypt's rate limit of 5 certs per domain per 168h. Switch to APP mode so a single certificate is requested per application, with the leader sharing it to peers.

Core changes

  • src/state/tls.py: TLSInformation.from_charm() now returns Optional[TLSInformation] and handles both leader and non-leader paths — leader reads from TLS library, non-leader reads from peer relation via _from_peer_relation(). PEER_TLS_KEY constant is defined here as the canonical location for TLS state.

  • src/charm.py: Switch TLSCertificatesRequiresV4 from Mode.UNIT to Mode.APP. All callers use TLSInformation.from_charm() directly for leader/non-leader branching. Add _reconcile_certificates() which writes certs to disk and, if leader, shares to peer relation app databag. Observe haproxy-peers relation-changed to trigger reconcile on non-leader units when cert data arrives.

  • src/tls_relation.py: Add share_certificates_via_peer_relation() to serialize cert+chain+key into peer relation app databag. Remove the certificate_requests guard from certificate_available() since non-leader units receive certs from peers, not the TLS library.

Data flow

Leader unit:
  TLS library → get_assigned_certificates() → write to disk → share to peer relation app databag

Non-leader unit:
  peer relation-changed → TLSInformation.from_charm() reads peer relation app databag → write to disk

Test changes

  • Updated certificates_integration fixture to use local_app_data (APP mode stores CSRs in app databag, not unit databag)
  • Existing tests that exercise TLS-dependent paths now set leader=True
  • Added 7 new tests covering: leader shares certs to peer relation, non-leader reads from peer relation via from_charm(), non-leader with haproxy-route mode, round-trip serialization of TLSInformation, empty peer data returns None

Copilot AI and others added 3 commits April 13, 2026 10:31
- Changed TLSCertificatesRequiresV4 mode from Mode.UNIT to Mode.APP
- Added certificate sharing via haproxy-peer relation app databag
- Leader unit shares certificate data (cert, chain, private key) to peers
- Non-leader units read certificate data from peer relation
- Added _get_tls_information() helper for leader/non-leader branching
- Added _reconcile_certificates() to handle sharing after writing to disk
- Hooked peer relation-changed event to reconcile loop
- Updated test fixtures for APP mode (local_app_data for CSR data, leader=True)

Agent-Logs-Url: https://github.com/canonical/haproxy-operator/sessions/984ade73-92cb-4e20-89e1-2f2c22483668

Co-authored-by: Thanhphan1147 <42444001+Thanhphan1147@users.noreply.github.com>
- Test leader shares certificates to peer relation app databag
- Test non-leader reads certificates from peer relation
- Test non-leader without peer data skips TLS gracefully
- Test non-leader haproxy-route reads from peer relation
- Test share_certificates_via_peer_relation method directly
- Test get_tls_information_from_peer_relation with data
- Test get_tls_information_from_peer_relation without data returns None

Agent-Logs-Url: https://github.com/canonical/haproxy-operator/sessions/984ade73-92cb-4e20-89e1-2f2c22483668

Co-authored-by: Thanhphan1147 <42444001+Thanhphan1147@users.noreply.github.com>
- Fix ruff RUF059: prefix unused variable with underscore
- Fix ruff RUF015: use next() instead of list comprehension slice
- Fix mypy union-attr: add assertions for optional relation types
- Improve type annotation: use Application instead of typing.Any

Agent-Logs-Url: https://github.com/canonical/haproxy-operator/sessions/984ade73-92cb-4e20-89e1-2f2c22483668

Co-authored-by: Thanhphan1147 <42444001+Thanhphan1147@users.noreply.github.com>
Copilot AI changed the title [WIP] Enhance certificate sharing for HA deployment Switch TLS certificates to APP mode and share via peer relation Apr 13, 2026
Copilot AI requested a review from Thanhphan1147 April 13, 2026 10:41
Comment thread haproxy-operator/src/charm.py Outdated
Comment thread haproxy-operator/src/charm.py Outdated
self.haproxy_service.reconcile_default(charm_state)
self.unit.status = ops.ActiveStatus()

def _get_tls_information(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This logic needs to be moved to the from_charm method of the TLSInformation dataclass.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the leader/non-leader branching logic into TLSInformation.from_charm() in state/tls.py. The method now returns Optional[TLSInformation] — leader path reads from the TLS library, non-leader path reads from the peer relation via _from_peer_relation(). Removed _get_tls_information() from charm.py and get_tls_information_from_peer_relation() from TLSRelationService. Also moved PEER_TLS_KEY constant to state/tls.py. See commit a3d6e5a.

Copilot AI and others added 2 commits April 14, 2026 11:34
…harm

- Moved _get_tls_information() logic from charm.py into TLSInformation.from_charm()
  in state/tls.py per reviewer feedback
- from_charm() now returns Optional[TLSInformation]: leader reads from TLS library,
  non-leader reads from peer relation via new _from_peer_relation() classmethod
- Removed _get_tls_information() method from charm.py
- Moved PEER_TLS_KEY constant to state/tls.py (canonical location for TLS state)
- Removed get_tls_information_from_peer_relation() from TLSRelationService
  (logic now in TLSInformation._from_peer_relation)
- Updated tests to test through TLSInformation.from_charm()

Agent-Logs-Url: https://github.com/canonical/haproxy-operator/sessions/96fecf67-9c28-47d1-8590-78ac4718d7c2

Co-authored-by: Thanhphan1147 <42444001+Thanhphan1147@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Test results for commit 895766a

Test coverage for 895766a

Name                                       Stmts   Miss Branch BrPart  Cover   Missing
--------------------------------------------------------------------------------------
lib/charms/haproxy/v0/ddos_protection.py     154     51     34      8    64%   157-174, 183-187, 288, 316-318, 323, 326-330, 342-344, 381, 387, 393, 396, 424, 495-498, 510-529
src/charm.py                                  21      0      0      0   100%
src/state.py                                  43      0      0      0   100%
--------------------------------------------------------------------------------------
TOTAL                                        218     51     34      8    73%

Static code analysis report

Run started:2026-05-01 15:41:13.860347+00:00

Test results:
  No issues identified.

Code scanned:
  Total lines of code: 333
  Total lines skipped (#nosec): 0
  Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
  Total issues (by severity):
  	Undefined: 0
  	Low: 0
  	Medium: 0
  	High: 0
  Total issues (by confidence):
  	Undefined: 0
  	Low: 0
  	Medium: 0
  	High: 0
Files skipped (0):

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Test results for commit 895766a

Test coverage for 895766a

Name                                         Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------------------------------------
lib/charms/haproxy/v0/ddos_protection.py       154     42     34      3    72%   157-174, 183-187, 265, 284, 415-418, 422-424, 459-478, 514-529
lib/charms/haproxy/v0/spoe_auth.py             158     55     32      2    59%   203, 304-306, 315, 354-381, 392-402, 441-442, 459-472, 484-501, 522-525, 529-531
lib/charms/haproxy/v1/haproxy_route_tcp.py     385    153     78      8    56%   209, 212, 281, 290-293, 297-300, 318-321, 336, 342-347, 447, 452, 829-832, 836, 863-874, 897-900, 904-906, 926-928, 1042-1083, 1087-1093, 1097, 1166-1195, 1266-1305, 1335-1337, 1362-1364, 1386-1390, 1409-1411, 1429-1431, 1438-1444, 1452-1454, 1462-1463, 1474-1481, 1494-1505, 1513-1534, 1546-1547, 1558-1559, 1570-1573, 1584-1585, 1614-1623, 1639-1642, 1658-1669, 1685-1688, 1706-1717, 1728-1729, 1737-1738, 1746-1747, 1758-1761
lib/charms/haproxy/v2/haproxy_route.py         386     53     98     26    82%   181, 257, 266-269, 294-297, 318-323, 673-674, 867->exit, 874, 900-911, 934-937, 941-943, 962-964, 1136-1142, 1146, 1343->1345, 1347->1349, 1349->1351, 1351->1353, 1353->1355, 1355->1358, 1393, 1401, 1406, 1409, 1434, 1462, 1466, 1470, 1493, 1513, 1522-1523, 1525->exit, 1561-1563, 1583, 1597, 1602-1604
src/charm.py                                   312     81    102     15    69%   102, 229, 237-253, 258, 263, 281, 292, 297-299, 325-326, 348-368, 387, 407-408, 491, 498-506, 534-549, 562-569, 578, 591-592, 599, 609, 619, 625-631, 647, 698-701, 707->706, 720-723
src/haproxy.py                                 125     31      6      2    75%   108-114, 134-156, 266-267, 270, 278-284, 312, 342-353, 365-367, 377-378
src/http_interface.py                           73     25      4      0    62%   74, 83, 92, 106-108, 126, 138, 150, 162, 170-175, 187, 194, 202, 217-227
src/state/charm_state.py                        78     15     14      4    79%   94-96, 101-102, 105, 150-155, 164, 216-218, 230-231
src/state/ddos_protection.py                    39      0      2      0   100%
src/state/exception.py                           1      0      0      0   100%
src/state/ha.py                                 30      1      2      1    94%   50
src/state/haproxy_route.py                     284     46     76      8    82%   161, 190-199, 256, 281, 332-360, 369, 378, 387, 396, 408, 446-472, 528, 568, 584-585, 602, 817-830
src/state/haproxy_route_tcp.py                 120     17     42      1    80%   92-94, 109->112, 147-160
src/state/ingress.py                            38      0      4      0   100%
src/state/ingress_per_unit.py                   32      0      4      0   100%
src/state/spoe_auth.py                          26      2      2      0    93%   63-64
src/state/tls.py                                64      7     20      4    87%   86, 114-115, 172-180, 186-187
src/state/validation.py                         46     23      8      1    44%   66-67, 71-98
src/tls_relation.py                             67      3     14      3    93%   85->84, 142-152, 164->166
----------------------------------------------------------------------------------------
TOTAL                                         2418    554    542     78    74%

Static code analysis report

Working... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
Run started:2026-05-01 15:41:40.487176+00:00

Test results:
  No issues identified.

Code scanned:
  Total lines of code: 10979
  Total lines skipped (#nosec): 13
  Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 10

Run metrics:
  Total issues (by severity):
  	Undefined: 0
  	Low: 0
  	Medium: 0
  	High: 0
  Total issues (by confidence):
  	Undefined: 0
  	Low: 0
  	Medium: 0
  	High: 0
Files skipped (0):

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Test results for commit 895766a

Test coverage for 895766a

Name                      Stmts   Miss Branch BrPart  Cover   Missing
---------------------------------------------------------------------
src/charm.py                 88     29     14      3    63%   97-98, 108-113, 138, 145-157, 161-176, 184-199
src/policy.py                66     37      2      0    43%   30-32, 37-38, 43-53, 58-59, 64-65, 78-92, 115-117, 140-141, 159-176, 187-197, 208-211
src/state/database.py        32      2      6      2    89%   76, 79
src/state/policy.py          62      2     10      2    94%   94, 106
src/state/validation.py      44     18      0      0    59%   57-59, 61-63, 74-87
---------------------------------------------------------------------
TOTAL                       292     88     32      7    68%

Static code analysis report

Run started:2026-05-01 15:41:17.261712+00:00

Test results:
  No issues identified.

Code scanned:
  Total lines of code: 1152
  Total lines skipped (#nosec): 10
  Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
  Total issues (by severity):
  	Undefined: 0
  	Low: 0
  	Medium: 0
  	High: 0
  Total issues (by confidence):
  	Undefined: 0
  	Low: 0
  	Medium: 0
  	High: 0
Files skipped (0):

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Test results for commit 895766a

Test coverage for 895766a

Name                                        Stmts   Miss  Cover
---------------------------------------------------------------
haproxy_route_policy/__init__.py                0      0   100%
haproxy_route_policy/settings.py               30      1    97%
haproxy_route_policy/test_settings.py           4      0   100%
haproxy_route_policy/urls.py                    5      0   100%
manage.py                                      11      2    82%
policy/__init__.py                              0      0   100%
policy/admin.py                                22      5    77%
policy/apps.py                                  3      0   100%
policy/db_models.py                            50      2    96%
policy/management/__init__.py                   0      0   100%
policy/management/commands/__init__.py          0      0   100%
policy/middleware.py                           16      4    75%
policy/migrations/0001_initial.py               7      0   100%
policy/migrations/0002_rule.py                  5      0   100%
policy/migrations/0003_alter_rule_kind.py       4      0   100%
policy/migrations/__init__.py                   0      0   100%
policy/rule_engine.py                          42      6    86%
policy/serializers.py                          30      4    87%
policy/tests/__init__.py                        0      0   100%
policy/tests/test_auth.py                      36     20    44%
policy/tests/test_models.py                    85      0   100%
policy/tests/test_rule_engine.py              117      0   100%
policy/tests/test_views.py                    201      0   100%
policy/urls.py                                  3      0   100%
policy/views.py                                95     16    83%
---------------------------------------------------------------
TOTAL                                         766     60    92%

Static code analysis report

Run started:2026-05-01 15:41:13.150727+00:00

Test results:
  No issues identified.

Code scanned:
  Total lines of code: 1680
  Total lines skipped (#nosec): 1
  Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
  Total issues (by severity):
  	Undefined: 0
  	Low: 0
  	Medium: 0
  	High: 0
  Total issues (by confidence):
  	Undefined: 0
  	Low: 0
  	Medium: 0
  	High: 0
Files skipped (0):

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Test results for commit 895766a

Unit tests failed for 895766a

```

============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.0.1, pluggy-1.6.0 -- /home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/bin/python3
cachedir: .tox/unit/.pytest_cache
rootdir: /home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator
configfile: pyproject.toml
collecting ... collected 1 item

tests/unit/test_charm.py::test_install FAILED

=================================== FAILURES ===================================
_________________________________ test_install _________________________________
Traceback (most recent call last):
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/scenario/_runtime.py", line 326, in exec
ops = Ops(
^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/scenario/_ops_main_mock.py", line 148, in init
super().init(self.charm_spec.charm_type, model_backend, juju_context=juju_context)
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/ops/_main.py", line 318, in init
self.charm = self._charm_class(self.framework)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/src/charm.py", line 47, in init
self.service = SpoeAuthService()
^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/src/haproxy_spoe_auth_service.py", line 48, in init
self.haproxy_spoe_auth_snap = cache[SNAP_NAME]
~~~~~^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/charmlibs/snap/_snap.py", line 965, in getitem
snap = self._snap_map[snap_name] = self._load_info(snap_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/charmlibs/snap/_snap.py", line 1014, in _load_info
info = self._snap_client.get_snap_information(name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/charmlibs/snap/_snap.py", line 914, in get_snap_information
return self._request('GET', 'find', {'name': name})[0] # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/charmlibs/snap/_snap.py", line 840, in _request
response = self._request_raw(method, path, query, headers, data)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/charmlibs/snap/_snap.py", line 888, in _request_raw
response = self.opener.open(request, timeout=self.timeout)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/urllib/request.py", line 515, in open
response = self._open(req, data)
^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/urllib/request.py", line 532, in _open
result = self._call_chain(self.handle_open, protocol, protocol +
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/urllib/request.py", line 492, in _call_chain
result = func(*args)
^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/charmlibs/snap/_snap.py", line 770, in http_open
return self.do_open(
^^^^^^^^^^^^^
File "/usr/lib/python3.12/urllib/request.py", line 1348, in do_open
r = h.getresponse()
^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/http/client.py", line 1448, in getresponse
response.begin()
File "/usr/lib/python3.12/http/client.py", line 336, in begin
version, status, reason = self._read_status()
^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/http/client.py", line 297, in _read_status
line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/socket.py", line 707, in readinto
return self._sock.recv_into(b)
^^^^^^^^^^^^^^^^^^^^^^^
TimeoutError: timed out

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/_pytest/runner.py", line 353, in from_call
result: TResult | None = func()
^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/_pytest/runner.py", line 245, in
lambda: runtest_hook(item=item, **kwds),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/pluggy/_hooks.py", line 512, in call
return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/pluggy/_callers.py", line 167, in _multicall
raise exception
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
teardown.throw(exception)
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/_pytest/logging.py", line 850, in pytest_runtest_call
yield
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
teardown.throw(exception)
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/_pytest/capture.py", line 900, in pytest_runtest_call
return (yield)
^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
teardown.throw(exception)
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/_pytest/skipping.py", line 268, in pytest_runtest_call
return (yield)
^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/pluggy/_callers.py", line 121, in _multicall
res = hook_impl.function(*args)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/_pytest/runner.py", line 179, in pytest_runtest_call
item.runtest()
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/_pytest/python.py", line 1720, in runtest
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/pluggy/_hooks.py", line 512, in call
return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/pluggy/_callers.py", line 167, in _multicall
raise exception
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/pluggy/_callers.py", line 121, in _multicall
res = hook_impl.function(*args)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/_pytest/python.py", line 166, in pytest_pyfunc_call
result = testfunction(**testargs)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/tests/unit/test_charm.py", line 22, in test_install
context.run(context.on.install(), state)
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/scenario/context.py", line 877, in run
with self._run(event=event, state=state) as ops:
File "/usr/lib/python3.12/contextlib.py", line 137, in enter
return next(self.gen)
^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/scenario/context.py", line 904, in _run
with runtime.exec(
File "/usr/lib/python3.12/contextlib.py", line 137, in enter
return next(self.gen)
^^^^^^^^^^^^^^
File "/home/runner/work/haproxy-operator/haproxy-operator/haproxy-spoe-auth-operator/.tox/unit/lib/python3.12/site-packages/scenario/_runtime.py", line 353, in exec
raise UncaughtCharmError(f'Uncaught {type(e).name} in charm, try "exceptions [n]" if using pdb on Python 3.13+. Details: {e!r}') from e # fmt: skip # noqa: E501
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
scenario.errors.UncaughtCharmError: Uncaught TimeoutError in charm, try "exceptions [n]" if using pdb on Python 3.13+. Details: TimeoutError('timed out')
------------------------------ Captured log call -------------------------------
DEBUG root:_main.py:332 ops 3.7.0 up and running.
DEBUG root:storage.py:68 Initializing SQLite local storage: :memory:.
=========================== short test summary info ============================
FAILED tests/unit/test_charm.py::test_install - scenario.errors.UncaughtCharmError: Uncaught TimeoutError in charm, try "ex...
============================== 1 failed in 31.41s ==============================

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Test results for commit 895766a

Test coverage for 895766a

Name                               Stmts   Miss Branch BrPart  Cover   Missing
------------------------------------------------------------------------------
src/charm.py                          45     24      2      0    45%   48-57, 61-98
src/haproxy_spoe_auth_service.py      44     16      2      0    61%   56-64, 76-82, 93-117
src/state.py                          55     29      6      0    43%   59-88, 125-146
------------------------------------------------------------------------------
TOTAL                                144     69     10      0    49%

Static code analysis report

Run started:2026-05-01 15:41:42.091848

Test results:
  No issues identified.

Code scanned:
  Total lines of code: 409
  Total lines skipped (#nosec): 1
  Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 1

Run metrics:
  Total issues (by severity):
  	Undefined: 0
  	Low: 0
  	Medium: 0
  	High: 0
  Total issues (by confidence):
  	Undefined: 0
  	Low: 0
  	Medium: 0
  	High: 0
Files skipped (0):

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Share single certificate between units in HA deployment

2 participants