Skip to content

Commit 1462fa1

Browse files
authored
Treat pre-releases as final releases in feature gates (#4296)
`_parse_version` returned `None` for any pre-release or dev build, which `_check_min_version` interprets as "latest", opening every feature gate. Since we publish rc builds (e.g. `0.21.4rc1`), a shim or runner on an rc was assumed to support features it does not have. For example, a shim on `0.20.25rc1` passed the `(0, 21, 3)` gate for restart-safe RUNNING tasks, letting the server restart a shim with running jobs on it. Ignore the pre-release, dev, post-release, and local segments instead, so `0.20.1rc1` is treated as `0.20.1`. An rc is cut from the release branch, so it normally carries the features of the version it leads to. Bare run numbers of staging builds (e.g. `1234`) still resolve to "latest".
1 parent abb8ce8 commit 1462fa1

2 files changed

Lines changed: 20 additions & 16 deletions

File tree

src/dstack/_internal/server/services/runner/client.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -855,20 +855,17 @@ def _is_json_response(response: requests.Response) -> bool:
855855

856856
def _parse_version(version_string: str) -> Optional[_Version]:
857857
"""
858-
Returns a (major, minor, micro) tuple if the version if final.
859-
Returns `None`, which means "latest", if:
860-
* the version is prerelease or dev build -- assuming that in most cases it's a build based on
861-
the latest final release
862-
* the version consists of only major part or not valid at all, e.g., staging builds have
863-
GitHub run number (e.g., 1234) instead of the version -- assuming that it's a "bleeding edge",
864-
not yet released version
858+
Returns a (major, minor, micro) tuple for feature gating. The pre-release, dev, post-release,
859+
and local segments are ignored, that is, `0.20.1rc1` is treated as `0.20.1` -- assuming that
860+
a build carrying a version has the features released in that version.
861+
Returns `None`, which means "latest", if the version consists of only major part or not valid
862+
at all, e.g., staging builds have GitHub run number (e.g., 1234) instead of the version
863+
-- assuming that it's a "bleeding edge", not yet released version.
865864
"""
866865
try:
867866
version = packaging.version.parse(version_string)
868867
except packaging.version.InvalidVersion:
869868
return None
870-
if version.is_prerelease or version.is_devrelease:
871-
return None
872869
release = version.release
873870
if len(release) <= 1:
874871
return None

src/tests/_internal/server/services/runner/test_client.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,9 @@ class TestShimClientNegotiate(BaseShimClientTest):
241241
# invalid versions, assuming local builds with the latest version
242242
pytest.param(None, 2, marks=pytest.mark.shim_version("latest")),
243243
pytest.param(None, 2, marks=pytest.mark.shim_version("0.17.0-next")),
244-
# even though this version is less than _FUTURE_API_MIN_VERSION, for the sake of
245-
# simplicity we assume that any non-final version is the latest; normally, users
246-
# should not use non-latest RC versions
247-
pytest.param(None, 2, marks=pytest.mark.shim_version("0.17.0rc1")),
244+
# pre-release versions are treated as the final version they lead to
245+
pytest.param((0, 17, 0), 1, marks=pytest.mark.shim_version("0.17.0rc1")),
246+
pytest.param((0, 18, 34), 2, marks=pytest.mark.shim_version("0.18.34rc1")),
248247
],
249248
)
250249
def test(
@@ -687,9 +686,17 @@ class TestParseVersion:
687686
def test_valid_final(self, value: str, expected: tuple[int, int, int]):
688687
assert _parse_version(value) == expected
689688

690-
@pytest.mark.parametrize("value", ["1.12alpha1", "1.12.3rc1", "1.12.3.dev0"])
691-
def test_valid_pre_dev_local(self, value: str):
692-
assert _parse_version(value) is None
689+
@pytest.mark.parametrize(
690+
["value", "expected"],
691+
[
692+
["1.12alpha1", (1, 12, 0)],
693+
["1.12.3rc1", (1, 12, 3)],
694+
["1.12.3.dev0", (1, 12, 3)],
695+
["1.12.3.post1", (1, 12, 3)],
696+
],
697+
)
698+
def test_valid_pre_dev_post(self, value: str, expected: tuple[int, int, int]):
699+
assert _parse_version(value) == expected
693700

694701
@pytest.mark.parametrize("value", ["1", "1234"])
695702
def test_valid_major_only(self, value: str):

0 commit comments

Comments
 (0)