Skip to content

Commit 7b7c3bb

Browse files
committed
Filter ci matrix by dev-spec channel
A dispatched dev build folds its channel into the --dev-spec JSON, and the shared CI workflow then stops passing --dev-channel. bakery ci matrix filtered only by the explicit --dev-channel option, so a single-channel dispatch still emitted every channel's dev version. Only the matching channel got its version pinned. The others resolved to their channel head, producing surprise extra builds. Derive the matrix channel filter from the dev-spec channel when --dev-channel is absent. This matches the channel that _apply_dev_spec already uses to pin the version, so the filter and the pin agree.
1 parent eca9265 commit 7b7c3bb

2 files changed

Lines changed: 78 additions & 1 deletion

File tree

posit-bakery/posit_bakery/cli/ci.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ def matrix(
135135
c = BakeryConfig.from_context(context=context, settings=settings)
136136
images = [i for i in c.model.images if image_name is None or re.search(image_name, i.name) is not None]
137137

138+
# A --dev-spec carrying a channel implies the matrix should be filtered to that
139+
# channel. The shared CI workflow folds the dispatched channel into the dev-spec
140+
# and stops passing --dev-channel, so without this the other channels' dev versions
141+
# would still be emitted (only the matching one gets its version pinned).
142+
effective_dev_channel = settings.dev_channel
143+
if effective_dev_channel is None and settings.dev_spec is not None:
144+
effective_dev_channel = settings.dev_spec.channel
145+
138146
data = []
139147
for img in images:
140148
entry = {"image": img.name}
@@ -153,7 +161,7 @@ def matrix(
153161
# If EXCLUDE: fall through using img.versions (devVersions are appended
154162
# there by load_dev_versions). The dev_versions filter below handles the rest.
155163
for ver in versions:
156-
included, _ = ver.matches_dev_filter(dev_versions, dev_channel)
164+
included, _ = ver.matches_dev_filter(dev_versions, effective_dev_channel)
157165
if not included:
158166
continue
159167
if image_version is not None and not version_matches(ver.name, image_version):

posit-bakery/test/cli/test_ci_matrix_dev_versions.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,72 @@ def test_prod_versions_excluded(self, mock_config_with_matrix_dev_image):
171171
versions_in_output = {e["version"] for e in data}
172172
assert prod_ver1.name not in versions_in_output
173173
assert prod_ver2.name not in versions_in_output
174+
175+
176+
@pytest.fixture
177+
def mock_config_with_two_channel_dev_image():
178+
"""Patch BakeryConfig to return one non-matrix image carrying both a
179+
daily and a preview dev version (the workbench/session-init shape)."""
180+
daily = _make_version("2026.99.0+237", is_dev=True, channel=ReleaseChannelEnum.DAILY)
181+
preview = _make_version("2026.99.0+240", is_dev=True, channel=ReleaseChannelEnum.PREVIEW)
182+
img = MagicMock()
183+
img.name = "workbench"
184+
img.matrix = None
185+
img.versions = [daily, preview]
186+
187+
with patch("posit_bakery.cli.ci.BakeryConfig") as mock:
188+
instance = MagicMock()
189+
instance.model.images = [img]
190+
mock.from_context.return_value = instance
191+
yield mock, daily, preview
192+
193+
194+
class TestCiMatrixDevSpecChannelFilter:
195+
"""A --dev-spec carrying a channel filters the matrix to that channel even
196+
when --dev-channel is omitted. The shared workflow folds the channel into
197+
the dev-spec and drops --dev-channel, so the matrix must honor it."""
198+
199+
def test_filters_to_dev_spec_channel(self, mock_config_with_two_channel_dev_image):
200+
_, daily, preview = mock_config_with_two_channel_dev_image
201+
result = runner.invoke(
202+
app,
203+
[
204+
"ci",
205+
"matrix",
206+
"--context",
207+
BASIC_CONTEXT,
208+
"--dev-versions",
209+
"only",
210+
"--dev-spec",
211+
'{"version": "2026.99.0+240", "channel": "preview"}',
212+
],
213+
catch_exceptions=False,
214+
)
215+
assert result.exit_code == 0, result.output
216+
data = json.loads(result.stdout.strip())
217+
versions = {e["version"] for e in data}
218+
assert preview.name in versions
219+
assert daily.name not in versions
220+
221+
def test_branch_only_dev_spec_does_not_filter(self, mock_config_with_two_channel_dev_image):
222+
"""A branch-only dev-spec carries no channel, so all channels stay in the matrix."""
223+
_, daily, preview = mock_config_with_two_channel_dev_image
224+
result = runner.invoke(
225+
app,
226+
[
227+
"ci",
228+
"matrix",
229+
"--context",
230+
BASIC_CONTEXT,
231+
"--dev-versions",
232+
"only",
233+
"--dev-spec",
234+
'{"release_branch": "2026.06"}',
235+
],
236+
catch_exceptions=False,
237+
)
238+
assert result.exit_code == 0, result.output
239+
data = json.loads(result.stdout.strip())
240+
versions = {e["version"] for e in data}
241+
assert preview.name in versions
242+
assert daily.name in versions

0 commit comments

Comments
 (0)