fix(ci): merge the matrix row's disable tokens with the caller's list

Both deploy steps set `disable: ${{ matrix.disable || inputs.disable }}`.
A row carries tokens of its own whenever its network mode needs them (a
clearnet row disables Tor), so the caller's list never reached the
deploy. In CI run 40979252521 the workspace swarm zombie for
svc-db-mariadb/postgres deployed 19 apps instead of 7 on its clearnet
row (fedora: 4h06 for one step, job cancelled at 345 min), while the
debian multi row with no tokens got 7, like main (32m44).

The steps now pass format('{0} {1}', matrix.disable, inputs.disable);
every reader splits on spaces and commas and drops duplicates and blank
keys. Both input descriptions follow. test_deploy_disable_merge.py reads
the live expressions; against the old fallback it fails two subtests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5.5 committed 2026-10-30 10:28:38 +01:00
1 parent 46962b5110
commit 57e574ac2d
2 files changed
+80 -4

No files matched your search

+4 -4
View File
@@ -75,7 +75,7 @@ on:
type: string
default: ""
disable:
description: "Comma-separated provider keys removed from the test inventory; used when the matrix row carries none of its own."
description: "Comma-separated provider keys removed from the test inventory, on top of the matrix row's own tokens."
required: false
type: string
default: ""
@@ -144,7 +144,7 @@ on:
type: string
default: ""
disable:
description: "🚫 Provider keys removed from the test inventory (comma-separated)"
description: "🚫 Provider keys removed from the test inventory on top of the matrix row's own tokens (comma-separated)"
required: false
type: string
default: ""
@@ -374,7 +374,7 @@ jobs:
shell: bash
env:
variant: ${{ matrix.variant }}
disable: ${{ matrix.disable || inputs.disable }}
disable: ${{ format('{0} {1}', matrix.disable, inputs.disable) }}
network: ${{ matrix.network }}
INFINITO_SWARM_VPN: ${{ matrix.vpn }}
INFINITO_SWARM_MESHES: ${{ inputs.meshes }}
@@ -387,7 +387,7 @@ jobs:
env:
apps: ${{ matrix.apps }}
variant: ${{ matrix.variant }}
disable: ${{ matrix.disable || inputs.disable }}
disable: ${{ format('{0} {1}', matrix.disable, inputs.disable) }}
network: ${{ matrix.network }}
ANSIBLE_LOG_PATH: /tmp/ansible-${{ matrix.apps }}.log
run: ./scripts/tests/deploy/ci/all.sh 2>&1 | tee "/tmp/ansible-${{ matrix.apps }}.log"
@@ -0,0 +1,76 @@
from __future__ import annotations
import re
import unittest
from cli.administration.inventory.provision.services_disabler import (
parse_services_disabled,
)
from tests.utils import PROJECT_ROOT
from utils.cache.files import read_text
DEPLOY_WORKFLOW = PROJECT_ROOT / ".github" / "workflows" / "call-test-deploy.yml"
_DISABLE_ENV = re.compile(r"^\s+disable:\s*\$\{\{\s*(.*?)\s*\}\}\s*$")
_FORMAT = re.compile(
r"format\('(?P<template>[^']*)',\s*matrix\.disable,\s*inputs\.disable\)"
)
_FALLBACK = re.compile(r"matrix\.disable\s*\|\|\s*inputs\.disable")
OLD_EXPRESSION = "matrix.disable || inputs.disable"
def disable_expressions() -> list[str]:
return [
match.group(1)
for line in read_text(str(DEPLOY_WORKFLOW)).splitlines()
if not line.lstrip().startswith("#")
for match in [_DISABLE_ENV.match(line)]
if match
]
def evaluate(expression: str, row: str, passed: str) -> list[str]:
"""The provider keys a step receives, as the deploy parses them.
Args:
expression: the step-env expression, without the ``${{ }}`` wrapper.
row: the matrix row's ``disable`` value.
passed: the caller's ``disable`` input.
"""
template = _FORMAT.fullmatch(expression)
if template:
return parse_services_disabled(template.group("template").format(row, passed))
if _FALLBACK.fullmatch(expression):
return parse_services_disabled(row or passed)
raise AssertionError(f"unsupported disable expression: {expression}")
class TestDeployDisableMerge(unittest.TestCase):
def test_both_deploy_steps_set_disable(self) -> None:
self.assertEqual(len(disable_expressions()), 2)
def test_row_tokens_keep_the_passed_list(self) -> None:
for expression in disable_expressions():
with self.subTest(expression=expression):
self.assertEqual(
evaluate(expression, "tor", "docs,matomo"),
["tor", "docs", "matomo"],
)
def test_passed_list_reaches_a_row_without_tokens(self) -> None:
for expression in disable_expressions():
with self.subTest(expression=expression):
self.assertEqual(evaluate(expression, "", "docs"), ["docs"])
def test_row_tokens_reach_a_caller_without_a_list(self) -> None:
for expression in disable_expressions():
with self.subTest(expression=expression):
self.assertEqual(evaluate(expression, "tor", ""), ["tor"])
def test_the_old_fallback_drops_the_passed_list(self) -> None:
self.assertEqual(evaluate(OLD_EXPRESSION, "tor", "docs,matomo"), ["tor"])
if __name__ == "__main__":
unittest.main()