16
16
import base64
17
17
import os
18
18
import re
19
+ import shutil
19
20
import subprocess
21
+ from tempfile import TemporaryDirectory
20
22
from typing import Any , Dict , List , Optional
21
23
from pydantic import BaseModel , ConfigDict
22
24
23
- from sagemaker .local .data import LocalFileDataSource , S3DataSource
24
25
from sagemaker .local .image import (
25
26
_Volume ,
26
27
_aws_credentials ,
34
35
from sagemaker .modules import logger
35
36
from sagemaker .modules .configs import Channel
36
37
from sagemaker .session import Session
37
- from sagemaker .utils import ECR_URI_PATTERN , create_tar_file , _module_import_error
38
+ from sagemaker .utils import ECR_URI_PATTERN , create_tar_file , _module_import_error , download_folder
38
39
from sagemaker_core .main .utils import Unassigned
39
40
from sagemaker_core .shapes import DataSource
40
41
@@ -420,8 +421,9 @@ def _generate_compose_command(self, wait: bool):
420
421
Args:
421
422
wait (bool): Whether to wait for the docker command result.
422
423
"""
423
- command = [
424
- "docker-compose" ,
424
+ _compose_cmd_prefix = self ._get_compose_cmd_prefix ()
425
+
426
+ command = _compose_cmd_prefix + [
425
427
"-f" ,
426
428
os .path .join (self .container_root , DOCKER_COMPOSE_FILENAME ),
427
429
"up" ,
@@ -502,8 +504,8 @@ def _prepare_training_volumes(
502
504
channel_dir = os .path .join (data_dir , channel_name )
503
505
os .makedirs (channel_dir , exist_ok = True )
504
506
505
- data_source_instance = self ._get_data_source_instance (channel .data_source )
506
- volumes .append (_Volume (data_source_instance . get_root_dir () , channel = channel_name ).map )
507
+ data_source_local_path = self ._get_data_source_local_path (channel .data_source )
508
+ volumes .append (_Volume (data_source_local_path , channel = channel_name ).map )
507
509
508
510
# If there is a training script directory and it is a local directory,
509
511
# mount it to the container.
@@ -518,23 +520,68 @@ def _prepare_training_volumes(
518
520
519
521
return volumes
520
522
521
- def _get_data_source_instance (self , data_source : DataSource ):
522
- """Return an Instance of :class:`sagemaker.local.data.DataSource`.
523
-
524
- The instance can handle the provided data_source URI.
523
+ def _get_data_source_local_path (self , data_source : DataSource ):
524
+ """Return a local data path of :class:`sagemaker.local.data.DataSource`.
525
525
526
- data_source can be either file:// or s3://
526
+ If the data source is from S3, the data will be downloaded to a temporary
527
+ local path.
528
+ If the data source is local file, the absolute path will be returned.
527
529
528
530
Args:
529
531
data_source (DataSource): a data source of local file or s3
530
532
531
533
Returns:
532
- sagemaker.local.data.DataSource: an Instance of a Data Source
534
+ str: The local path of the data.
533
535
"""
534
536
if data_source .s3_data_source != Unassigned ():
535
537
uri = data_source .s3_data_source .s3_uri
536
538
parsed_uri = urlparse (uri )
537
- return S3DataSource (parsed_uri .netloc , parsed_uri .path , self .sagemaker_session )
539
+ local_dir = TemporaryDirectory (prefix = os .path .join (self .container_root + "/" )).name
540
+ download_folder (parsed_uri .netloc , parsed_uri .path , local_dir , self .sagemaker_session )
541
+ return local_dir
538
542
else :
539
- uri = data_source .file_system_data_source .directory_path
540
- return LocalFileDataSource (uri )
543
+ return os .path .abspath (data_source .file_system_data_source .directory_path )
544
+
545
+ def _get_compose_cmd_prefix (self ) -> List [str ]:
546
+ """Gets the Docker Compose command.
547
+
548
+ The method initially looks for 'docker compose' v2
549
+ executable, if not found looks for 'docker-compose' executable.
550
+
551
+ Returns:
552
+ List[str]: Docker Compose executable split into list.
553
+
554
+ Raises:
555
+ ImportError: If Docker Compose executable was not found.
556
+ """
557
+ compose_cmd_prefix = []
558
+
559
+ output = None
560
+ try :
561
+ output = subprocess .check_output (
562
+ ["docker" , "compose" , "version" ],
563
+ stderr = subprocess .DEVNULL ,
564
+ encoding = "UTF-8" ,
565
+ )
566
+ except subprocess .CalledProcessError :
567
+ logger .info (
568
+ "'Docker Compose' is not installed. "
569
+ "Proceeding to check for 'docker-compose' CLI."
570
+ )
571
+
572
+ if output and "v2" in output .strip ():
573
+ logger .info ("'Docker Compose' found using Docker CLI." )
574
+ compose_cmd_prefix .extend (["docker" , "compose" ])
575
+ return compose_cmd_prefix
576
+
577
+ if shutil .which ("docker-compose" ) is not None :
578
+ logger .info ("'Docker Compose' found using Docker Compose CLI." )
579
+ compose_cmd_prefix .extend (["docker-compose" ])
580
+ return compose_cmd_prefix
581
+
582
+ raise ImportError (
583
+ "Docker Compose is not installed. "
584
+ "Local Mode features will not work without docker compose. "
585
+ "For more information on how to install 'docker compose', please, see "
586
+ "https://docs.docker.com/compose/install/"
587
+ )
0 commit comments