Skip to content

Commit 0e06e41

Browse files
[ci] refactor push_ray_image shared functions to image_tags_lib (#60793)
There is significant overlap with the next feature of push_anyscale_image, so this refactor reduces on a lot of duplicated work. Signed-off-by: andrew <andrew@anyscale.com>
1 parent d408d33 commit 0e06e41

File tree

5 files changed

+313
-57
lines changed

5 files changed

+313
-57
lines changed

ci/ray_ci/automation/BUILD.bazel

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,31 @@ py_library(
114114
],
115115
)
116116

117+
py_library(
118+
name = "image_tags_lib",
119+
srcs = ["image_tags_lib.py"],
120+
visibility = ["//ci/ray_ci/automation:__subpackages__"],
121+
deps = [
122+
":crane_lib",
123+
"//ci/ray_ci:ray_ci_lib",
124+
],
125+
)
126+
127+
py_test(
128+
name = "test_image_tags_lib",
129+
size = "small",
130+
srcs = ["test_image_tags_lib.py"],
131+
exec_compatible_with = ["//bazel:py3"],
132+
tags = [
133+
"ci_unit",
134+
"team:ci",
135+
],
136+
deps = [
137+
":image_tags_lib",
138+
ci_require("pytest"),
139+
],
140+
)
141+
117142
py_library(
118143
name = "test_utils",
119144
srcs = ["test_utils.py"],
@@ -322,7 +347,7 @@ py_binary(
322347
srcs = ["push_ray_image.py"],
323348
exec_compatible_with = ["//bazel:py3"],
324349
deps = [
325-
":crane_lib",
350+
":image_tags_lib",
326351
"//ci/ray_ci:ray_ci_lib",
327352
"//release:ray_release",
328353
ci_require("click"),
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""
2+
Shared utilities for generating Docker image tags for Ray and Anyscale images.
3+
"""
4+
5+
import logging
6+
from typing import List
7+
8+
from ci.ray_ci.automation.crane_lib import (
9+
CraneError,
10+
call_crane_copy,
11+
call_crane_manifest,
12+
)
13+
from ci.ray_ci.configs import DEFAULT_PYTHON_TAG_VERSION
14+
from ci.ray_ci.docker_container import GPU_PLATFORM
15+
16+
logger = logging.getLogger(__name__)
17+
18+
19+
class ImageTagsError(Exception):
20+
"""Error raised when image tag operations fail."""
21+
22+
23+
def format_platform_tag(platform: str) -> str:
24+
"""
25+
Format platform as -cpu or shortened CUDA version.
26+
27+
Examples:
28+
cpu -> -cpu
29+
cu12.1.1-cudnn8 -> -cu121
30+
cu12.3.2-cudnn9 -> -cu123
31+
"""
32+
if platform == "cpu":
33+
return "-cpu"
34+
# cu12.3.2-cudnn9 -> -cu123
35+
platform_base = platform.split("-", 1)[0]
36+
parts = platform_base.split(".")
37+
if len(parts) < 2:
38+
raise ImageTagsError(f"Unrecognized GPU platform format: {platform}")
39+
return f"-{parts[0]}{parts[1]}"
40+
41+
42+
def format_python_tag(python_version: str) -> str:
43+
"""
44+
Format python version as -py310 (no dots, with hyphen prefix).
45+
46+
Examples:
47+
3.10 -> -py310
48+
3.11 -> -py311
49+
"""
50+
return f"-py{python_version.replace('.', '')}"
51+
52+
53+
def get_python_suffixes(python_version: str) -> List[str]:
54+
"""
55+
Get python version suffixes (includes empty for default version).
56+
57+
For DEFAULT_PYTHON_TAG_VERSION (3.10), returns both the explicit
58+
suffix and an empty suffix for backward compatibility.
59+
60+
Examples:
61+
3.10 -> ["-py310", ""]
62+
3.11 -> ["-py311"]
63+
"""
64+
suffixes = [format_python_tag(python_version)]
65+
if python_version == DEFAULT_PYTHON_TAG_VERSION:
66+
suffixes.append("")
67+
return suffixes
68+
69+
70+
def get_platform_suffixes(platform: str, image_type: str) -> List[str]:
71+
"""
72+
Get platform suffixes (includes aliases like -gpu for GPU_PLATFORM).
73+
74+
Handles the following cases:
75+
- CPU with ray/ray-extra: adds empty suffix alias
76+
- GPU_PLATFORM: adds -gpu alias
77+
- GPU_PLATFORM with ray-ml/ray-ml-extra: adds empty suffix alias
78+
79+
Args:
80+
platform: The platform string (e.g., "cpu", "cu12.1.1-cudnn8")
81+
image_type: The image type (e.g., "ray", "ray-ml", "ray-extra")
82+
83+
Returns:
84+
List of platform suffixes to use for tagging.
85+
"""
86+
platform_tag = format_platform_tag(platform)
87+
suffixes = [platform_tag]
88+
89+
if platform == "cpu":
90+
# no tag is alias to cpu for ray image
91+
if image_type in ("ray", "ray-extra"):
92+
suffixes.append("")
93+
elif platform == GPU_PLATFORM:
94+
# gpu is alias to GPU_PLATFORM
95+
suffixes.append("-gpu")
96+
# no tag is alias to gpu for ray-ml image
97+
if image_type in ("ray-ml", "ray-ml-extra"):
98+
suffixes.append("")
99+
100+
return suffixes
101+
102+
103+
def get_variation_suffix(image_type: str) -> str:
104+
"""
105+
Get variation suffix for -extra image types.
106+
107+
Examples:
108+
ray -> ""
109+
ray-extra -> "-extra"
110+
ray-ml-extra -> "-extra"
111+
"""
112+
if image_type in ("ray-extra", "ray-ml-extra", "ray-llm-extra"):
113+
return "-extra"
114+
return ""
115+
116+
117+
def image_exists(tag: str) -> bool:
118+
"""Check if a container image manifest exists using crane."""
119+
try:
120+
call_crane_manifest(tag)
121+
return True
122+
except CraneError:
123+
return False
124+
125+
126+
def copy_image(source: str, destination: str, dry_run: bool = False) -> None:
127+
"""Copy a container image from source to destination using crane."""
128+
if dry_run:
129+
logger.info(f"DRY RUN: Would copy {source} -> {destination}")
130+
return
131+
132+
logger.info(f"Copying {source} -> {destination}")
133+
try:
134+
call_crane_copy(source, destination)
135+
logger.info(f"Successfully copied to {destination}")
136+
except CraneError as e:
137+
raise ImageTagsError(f"Crane copy failed: {e}")

ci/ray_ci/automation/push_ray_image.py

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,24 @@
55

66
import click
77

8-
from ci.ray_ci.automation.crane_lib import (
9-
CraneError,
10-
call_crane_copy,
11-
call_crane_manifest,
8+
from ci.ray_ci.automation.image_tags_lib import (
9+
ImageTagsError,
10+
copy_image,
11+
format_platform_tag,
12+
get_platform_suffixes,
13+
get_python_suffixes,
14+
get_variation_suffix,
15+
image_exists,
1216
)
1317
from ci.ray_ci.configs import (
1418
ARCHITECTURE,
1519
DEFAULT_ARCHITECTURE,
16-
DEFAULT_PYTHON_TAG_VERSION,
1720
PYTHON_VERSIONS,
1821
)
1922
from ci.ray_ci.docker_container import (
2023
ARCHITECTURES_RAY,
2124
ARCHITECTURES_RAY_LLM,
2225
ARCHITECTURES_RAY_ML,
23-
GPU_PLATFORM,
2426
PLATFORMS_RAY,
2527
PLATFORMS_RAY_LLM,
2628
PLATFORMS_RAY_ML,
@@ -48,14 +50,10 @@ class PushRayImageError(Exception):
4850
"""Error raised when pushing ray images fails."""
4951

5052

53+
# Re-export for backward compatibility with tests
5154
def compact_cuda_suffix(platform: str) -> str:
5255
"""Convert a CUDA platform string to compact suffix (e.g. cu12.1.1-cudnn8 -> -cu121)."""
53-
platform_base = platform.split("-", 1)[0]
54-
parts = platform_base.split(".")
55-
if len(parts) < 2:
56-
raise PushRayImageError(f"Unrecognized GPU platform format: {platform}")
57-
58-
return f"-{parts[0]}{parts[1]}"
56+
return format_platform_tag(platform)
5957

6058

6159
class RayImagePushContext:
@@ -188,62 +186,28 @@ def _versions(self) -> List[str]:
188186

189187
def _variation_suffix(self) -> str:
190188
"""Get -extra suffix for extra image types."""
191-
if self.ray_type in {
192-
RayType.RAY_EXTRA,
193-
RayType.RAY_ML_EXTRA,
194-
RayType.RAY_LLM_EXTRA,
195-
}:
196-
return "-extra"
197-
return ""
189+
return get_variation_suffix(self.ray_type.value)
198190

199191
def _python_suffixes(self) -> List[str]:
200192
"""Get python version suffixes (includes empty for default version)."""
201-
suffixes = [f"-py{self.python_version.replace('.', '')}"]
202-
if self.python_version == DEFAULT_PYTHON_TAG_VERSION:
203-
suffixes.append("")
204-
return suffixes
193+
return get_python_suffixes(self.python_version)
205194

206195
def _platform_suffixes(self) -> List[str]:
207196
"""Get platform suffixes (includes aliases like -gpu for GPU_PLATFORM)."""
208-
if self.platform == "cpu":
209-
suffixes = ["-cpu"]
210-
# no tag is alias to cpu for ray image
211-
if self.ray_type in {RayType.RAY, RayType.RAY_EXTRA}:
212-
suffixes.append("")
213-
return suffixes
214-
215-
suffixes = [compact_cuda_suffix(self.platform)]
216-
if self.platform == GPU_PLATFORM:
217-
# gpu is alias to GPU_PLATFORM value for ray image
218-
suffixes.append("-gpu")
219-
# no tag is alias to gpu for ray-ml image
220-
if self.ray_type in {RayType.RAY_ML, RayType.RAY_ML_EXTRA}:
221-
suffixes.append("")
222-
223-
return suffixes
197+
return get_platform_suffixes(self.platform, self.ray_type.value)
224198

225199

226200
def _image_exists(tag: str) -> bool:
227201
"""Check if a container image manifest exists using crane."""
228-
try:
229-
call_crane_manifest(tag)
230-
return True
231-
except CraneError:
232-
return False
202+
return image_exists(tag)
233203

234204

235205
def _copy_image(reference: str, destination: str, dry_run: bool = False) -> None:
236206
"""Copy a container image from source to destination using crane."""
237-
if dry_run:
238-
logger.info(f"DRY RUN: Would copy {reference} -> {destination}")
239-
return
240-
241-
logger.info(f"Copying {reference} -> {destination}")
242207
try:
243-
call_crane_copy(reference, destination)
244-
logger.info(f"Successfully copied to {destination}")
245-
except CraneError as e:
246-
raise PushRayImageError(f"Crane copy failed: {e}")
208+
copy_image(reference, destination, dry_run)
209+
except ImageTagsError as e:
210+
raise PushRayImageError(str(e))
247211

248212

249213
def _should_upload(pipeline_id: str, branch: str, rayci_schedule: str) -> bool:

0 commit comments

Comments
 (0)