99import tarfile
1010import tempfile
1111from io import BytesIO
12+ from pathlib import Path
1213from textwrap import dedent
1314from typing import Any , Dict , Generator , List , Mapping , Optional , Union
1415
1718except ImportError as e :
1819 zstd = e
1920
21+ from taskgraph .config import GraphConfig
2022from taskgraph .util import docker , json
2123from taskgraph .util .taskcluster import (
2224 find_task_id ,
@@ -353,7 +355,47 @@ def _index(l: List, s: str) -> Optional[int]:
353355 pass
354356
355357
356- def load_task (task_id : str , remove : bool = True , user : Optional [str ] = None ) -> int :
358+ def _resolve_image (image : Union [str , Dict [str , str ]], graph_config : GraphConfig ) -> str :
359+ image_task_id = None
360+
361+ # Standard case, image comes from the task definition.
362+ if isinstance (image , dict ):
363+ assert "type" in image
364+
365+ if image ["type" ] == "task-image" :
366+ image_task_id = image ["taskId" ]
367+ elif image ["type" ] == "indexed-image" :
368+ image_task_id = find_task_id (image ["namespace" ])
369+ else :
370+ raise Exception (f"Tasks with { image ['type' ]} images are not supported!" )
371+ else :
372+ # Check if image refers to an in-tree image under taskcluster/docker,
373+ # if so build it.
374+ image_dir = docker .image_path (image , graph_config )
375+ if Path (image_dir ).is_dir ():
376+ tag = f"taskcluster/{ image } :latest"
377+ build_image (image , tag , graph_config , os .environ )
378+ return tag
379+
380+ # Check if we're referencing a task or index.
381+ if image .startswith ("task-id=" ):
382+ image_task_id = image .split ("=" , 1 )[1 ]
383+ elif image .startswith ("index=" ):
384+ index = image .split ("=" , 1 )[1 ]
385+ image_task_id = find_task_id (index )
386+ else :
387+ raise Exception (f"Unable to resolve image '{ image } '!" )
388+
389+ return load_image_by_task_id (image_task_id )
390+
391+
392+ def load_task (
393+ graph_config : GraphConfig ,
394+ task_id : str ,
395+ remove : bool = True ,
396+ user : Optional [str ] = None ,
397+ custom_image : Optional [str ] = None ,
398+ ) -> int :
357399 """Load and run a task interactively in a Docker container.
358400
359401 Downloads the Docker image from a task's artifacts and runs it in an
@@ -362,9 +404,11 @@ def load_task(task_id: str, remove: bool = True, user: Optional[str] = None) ->
362404 the 'exec-task' function provided in the shell.
363405
364406 Args:
407+ graph_config: The graph configuration object.
365408 task_id: The ID of the task to load.
366409 remove: Whether to remove the container after exit (default True).
367410 user: The user to switch to in the container (default 'worker').
411+ custom_image: A custom image to use instead of the task's image.
368412
369413 Returns:
370414 int: The exit code from the Docker container.
@@ -386,6 +430,13 @@ def load_task(task_id: str, remove: bool = True, user: Optional[str] = None) ->
386430 print ("Only tasks using `run-task` are supported!" )
387431 return 1
388432
433+ try :
434+ image = custom_image or image
435+ image_tag = _resolve_image (image , graph_config )
436+ except Exception as e :
437+ print (e )
438+ return 1
439+
389440 # Remove the payload section of the task's command. This way run-task will
390441 # set up the task (clone repos, download fetches, etc) but won't actually
391442 # start the core of the task. Instead we'll drop the user into an interactive
@@ -414,16 +465,6 @@ def load_task(task_id: str, remove: bool = True, user: Optional[str] = None) ->
414465 else :
415466 task_cwd = "$TASK_WORKDIR"
416467
417- if image ["type" ] == "task-image" :
418- image_task_id = image ["taskId" ]
419- elif image ["type" ] == "indexed-image" :
420- image_task_id = find_task_id (image ["namespace" ])
421- else :
422- print (f"Tasks with { image ['type' ]} images are not supported!" )
423- return 1
424-
425- image_tag = load_image_by_task_id (image_task_id )
426-
427468 # Set some env vars the worker would normally set.
428469 env = {
429470 "RUN_ID" : "0" ,
0 commit comments