Skip to content

Commit 7c9b378

Browse files
committed
handle docker images
1 parent 13a68c9 commit 7c9b378

File tree

4 files changed

+91
-89
lines changed

4 files changed

+91
-89
lines changed

commit0/harness/build.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ def main(
3131

3232
client = docker.from_env()
3333
build_repo_images(client, specs, num_workers)
34+
for spec in specs:
35+
image = client.images.get(spec.repo_image_key)
36+
repository, tag = spec.repo_image_tag.split(":")
37+
image.tag(repository, tag)
3438

3539

3640
__all__ = []

commit0/harness/docker_utils.py

Lines changed: 74 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def write_to_container(container: Container, data: str, dst: Path) -> None:
126126
def cleanup_container(
127127
client: docker.DockerClient,
128128
container: Container,
129-
logger: Union[None, str, logging.Logger],
129+
logger: logging.Logger,
130130
) -> None:
131131
"""Stop and remove a Docker container.
132132
Performs this forcefully if the container cannot be stopped with the python API.
@@ -135,51 +135,21 @@ def cleanup_container(
135135
----
136136
client (docker.DockerClient): Docker client.
137137
container (docker.Container): Container to remove.
138-
logger (Union[str, logging.Logger], optional): Logger instance or log level as string for logging container creation messages. Defaults to None.
138+
logger (logging.Logger): Logger instance or log level as string for logging container creation messages.
139139
140140
"""
141141
if not container:
142142
return
143143

144144
container_id = container.id
145145

146-
if not logger:
147-
# if logger is None, print to stdout
148-
def log_error(x: str) -> None:
149-
print(x)
150-
151-
def log_info(x: str) -> None:
152-
print(x)
153-
154-
raise_error = True
155-
elif logger == "quiet":
156-
# if logger is "quiet", don't print anything
157-
def log_info(x: str) -> None:
158-
return None
159-
160-
def log_error(x: str) -> None:
161-
return None
162-
163-
raise_error = True
164-
else:
165-
assert isinstance(logger, logging.Logger)
166-
167-
# if logger is a logger object, use it
168-
def log_error(x: str) -> None:
169-
logger.info(x)
170-
171-
def log_info(x: str) -> None:
172-
logger.info(x)
173-
174-
raise_error = False
175-
176146
# Attempt to stop the container
177147
try:
178148
if container:
179-
log_info(f"Attempting to stop container {container.name}...")
149+
logger.info(f"Attempting to stop container {container.name}...")
180150
container.kill()
181151
except Exception as e:
182-
log_error(
152+
logger.error(
183153
f"Failed to stop container {container.name}: {e}. Trying to forcefully kill..."
184154
)
185155
try:
@@ -190,54 +160,103 @@ def log_info(x: str) -> None:
190160

191161
# If container PID found, forcefully kill the container
192162
if pid > 0:
193-
log_info(
163+
logger.info(
194164
f"Forcefully killing container {container.name} with PID {pid}..."
195165
)
196166
os.kill(pid, signal.SIGKILL)
197167
else:
198-
log_error(f"PID for container {container.name}: {pid} - not killing.")
168+
logger.error(f"PID for container {container.name}: {pid} - not killing.")
199169
except Exception as e2:
200-
if raise_error:
201-
raise e2
202-
log_error(
170+
raise Exception(
203171
f"Failed to forcefully kill container {container.name}: {e2}\n"
204172
f"{traceback.format_exc()}"
205173
)
206174

207175
# Attempt to remove the container
208176
try:
209-
log_info(f"Attempting to remove container {container.name}...")
177+
logger.info(f"Attempting to remove container {container.name}...")
210178
container.remove(force=True)
211-
log_info(f"Container {container.name} removed.")
179+
logger.info(f"Container {container.name} removed.")
212180
except Exception as e:
213-
if raise_error:
214-
raise e
215-
log_error(
181+
raise Exception(
216182
f"Failed to remove container {container.name}: {e}\n"
217183
f"{traceback.format_exc()}"
218184
)
219185

220186

187+
def image_exists_locally(client: docker.DockerClient, image_name: str, tag: str, logger: logging.Logger) -> bool:
188+
"""
189+
Check if a Docker image exists locally.
190+
191+
Args:
192+
----
193+
client (docker.DockerClient): Docker client instance.
194+
image_name (str): The name of the Docker image.
195+
tag (str, optional): Tag of the Docker image.
196+
logger (logging.Logger): Logger instance.
197+
198+
Returns:
199+
-------
200+
bool: True if the image exists locally, False otherwise.
201+
"""
202+
images = client.images.list(name=image_name)
203+
for image in images:
204+
if f"{image_name}:{tag}" in image.tags:
205+
logger.info(f"Using {image_name}:{tag} found locally.")
206+
return True
207+
logger.info(f"{image_name}:{tag} cannot be found locally")
208+
return False
209+
210+
def pull_image_from_docker_hub(client: docker.DockerClient, image_name: str, tag: str, logger: logging.Logger) -> docker.models.images.Image:
211+
"""
212+
Pull a Docker image from Docker Hub.
213+
214+
Args:
215+
----
216+
client (docker.DockerClient): Docker client instance.
217+
image_name (str): The name of the Docker image.
218+
tag (str, optional): Tag of the Docker image.
219+
logger (logging.Logger): Logger instance.
220+
221+
Returns:
222+
-------
223+
docker.models.images.Image: The pulled Docker image.
224+
225+
Raises:
226+
------
227+
docker.errors.ImageNotFound: If the image is not found on Docker Hub.
228+
docker.errors.APIError: If there's an issue with the Docker API during the pull.
229+
"""
230+
try:
231+
image = client.images.pull(image_name, tag=tag)
232+
logger.info(f"Loaded {image_name}:{tag} from Docker Hub.")
233+
return image
234+
except docker.errors.ImageNotFound:
235+
raise Exception(f"Image {image_name}:{tag} not found on Docker Hub.")
236+
except docker.errors.APIError as e:
237+
raise Exception(f"Error pulling image: {e}")
238+
239+
221240
def create_container(
222241
client: docker.DockerClient,
223242
image_name: str,
224-
container_name: Optional[str] = None,
243+
container_name: str,
244+
logger: logging.Logger,
225245
user: Optional[str] = None,
226246
command: Optional[str] = "tail -f /dev/null",
227247
nano_cpus: Optional[int] = None,
228-
logger: Optional[Union[str, logging.Logger]] = None,
229248
) -> Container:
230249
"""Start a Docker container using the specified image.
231250
232251
Args:
233252
----
234253
client (docker.DockerClient): Docker client.
235254
image_name (str): The name of the Docker image.
236-
container_name (str, optional): Name for the Docker container. Defaults to None.
255+
container_name (str): Name for the Docker container.
256+
logger (logging.Logger): Logger instance or log level as string for logging container creation messages.
237257
user (str, option): Log in as which user. Defaults to None.
238258
command (str, optional): Command to run in the container. Defaults to None.
239259
nano_cpus (int, optional): The number of CPUs for the container. Defaults to None.
240-
logger (Union[str, logging.Logger], optional): Logger instance or log level as string for logging container creation messages. Defaults to None.
241260
242261
Returns:
243262
-------
@@ -249,41 +268,13 @@ def create_container(
249268
Exception: For other general errors.
250269
251270
"""
252-
try:
253-
# Pull the image if it doesn't already exist
254-
client.images.pull(image_name)
255-
except docker.errors.APIError as e:
256-
raise docker.errors.APIError(f"Error pulling image: {str(e)}")
257-
258-
if not logger:
259-
# if logger is None, print to stdout
260-
def log_error(x: str) -> None:
261-
print(x)
262-
263-
def log_info(x: str) -> None:
264-
print(x)
265-
266-
elif logger == "quiet":
267-
# if logger is "quiet", don't print anything
268-
def log_info(x: str) -> None:
269-
return None
270-
271-
def log_error(x: str) -> None:
272-
return None
273-
274-
else:
275-
assert isinstance(logger, logging.Logger)
276-
277-
# if logger is a logger object, use it
278-
def log_error(x: str) -> None:
279-
logger.info(x)
280-
281-
def log_info(x: str) -> None:
282-
logger.info(x)
271+
image, tag = image_name.split(":")
272+
if not image_exists_locally(client, image, tag, logger):
273+
pull_image_from_docker_hub(client, image, tag, logger)
283274

284275
container = None
285276
try:
286-
log_info(f"Creating container for {image_name}...")
277+
logger.info(f"Creating container for {image_name}...")
287278
container = client.containers.run(
288279
image=image_name,
289280
name=container_name,
@@ -292,12 +283,12 @@ def log_info(x: str) -> None:
292283
nano_cpus=nano_cpus,
293284
detach=True,
294285
)
295-
log_info(f"Container for {image_name} created: {container.id}")
286+
logger.info(f"Container for {image_name} created: {container.id}")
296287
return container
297288
except Exception as e:
298289
# If an error occurs, clean up the container and raise an exception
299-
log_error(f"Error creating container for {image_name}: {e}")
300-
log_info(traceback.format_exc())
290+
logger.error(f"Error creating container for {image_name}: {e}")
291+
logger.info(traceback.format_exc())
301292
assert container is not None
302293
cleanup_container(client, container, logger)
303294
raise

commit0/harness/execution_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def __init__(
9999
self.client = docker.from_env()
100100
self.container = create_container(
101101
client=self.client,
102-
image_name=spec.repo_image_key,
102+
image_name=spec.repo_image_tag,
103103
container_name=spec.get_container_name(),
104104
nano_cpus=num_cpus,
105105
logger=logger,

commit0/harness/spec.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import hashlib
12
from dataclasses import dataclass
23
from typing import Union, cast, Optional
34

@@ -46,12 +47,18 @@ def repo_image_key(self) -> str:
4647
4748
Note that old images are not automatically deleted, so consider cleaning up old images periodically.
4849
"""
49-
# hash_object = hashlib.sha256()
50-
# hash_object.update(str(self.setup_script).encode("utf-8"))
51-
# hash_value = hash_object.hexdigest()
52-
# val = hash_value[:22] # 22 characters is still very likely to be unique
50+
hash_object = hashlib.sha256()
51+
hash_object.update(str(self.setup_script).encode("utf-8"))
52+
hash_value = hash_object.hexdigest()
53+
val = hash_value[:22] # 22 characters is still very likely to be unique
54+
repo = self.repo.split("/")[-1]
55+
# this is the image name created locally
56+
# once this image created, it will be tagged with repo_image_tag
57+
return f"commit0.repo.{repo}.{val}:latest".lower()
58+
59+
@property
60+
def repo_image_tag(self):
5361
repo = self.repo.split("/")[-1]
54-
# return f"commit0.repo.{repo}.{val}:latest".lower()
5562
return f"wentingzhao/{repo}:latest".lower()
5663

5764
def get_container_name(self, run_id: Optional[str] = None) -> str:

0 commit comments

Comments
 (0)