Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/dstack/_internal/cli/commands/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
EndpointPresetStore,
load_endpoint_configuration,
)
from dstack._internal.cli.services.profile import apply_profile_args, register_profile_args
from dstack._internal.cli.services.profile import (
apply_profile_args,
load_profile_from_args,
register_profile_args,
)
from dstack._internal.cli.utils.common import confirm_ask, console
from dstack._internal.core.errors import CLIError
from dstack._internal.core.models.profiles import ProfileParams
from dstack._internal.core.services import is_valid_dstack_resource_name
from dstack.api import Client
from dstack.api.utils import load_profile


class EndpointCommand(BaseCommand):
Expand Down Expand Up @@ -255,7 +258,7 @@ def _get_effective_configuration(
args: argparse.Namespace,
) -> EndpointConfiguration:
_apply_name(configuration, args.name)
profile = load_profile(Path.cwd(), args.profile)
profile = load_profile_from_args(args=args, repo_dir=Path.cwd())
for field in ProfileParams.__fields__:
if getattr(configuration, field) is None:
setattr(configuration, field, getattr(profile, field))
Expand Down
5 changes: 2 additions & 3 deletions src/dstack/_internal/cli/commands/offer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from dstack._internal.cli.services.configurators.run import (
BaseRunConfigurator,
)
from dstack._internal.cli.services.profile import register_profile_args
from dstack._internal.cli.services.profile import load_profile_from_args, register_profile_args
from dstack._internal.cli.services.resources import register_resources_args
from dstack._internal.cli.utils.common import console
from dstack._internal.cli.utils.gpu import print_gpu_json, print_gpu_table
Expand All @@ -15,7 +15,6 @@
from dstack._internal.core.models.configurations import ApplyConfigurationType, TaskConfiguration
from dstack._internal.core.models.gpus import GpuGroup
from dstack._internal.core.models.runs import RunSpec
from dstack.api.utils import load_profile


class OfferConfigurator(BaseRunConfigurator):
Expand Down Expand Up @@ -81,7 +80,7 @@ def _command(self, args: argparse.Namespace):

configurator = OfferConfigurator(api_client=self.api)
configurator.apply_args(conf, args)
profile = load_profile(Path.cwd(), profile_name=args.profile)
profile = load_profile_from_args(args=args, repo_dir=Path.cwd())

run_spec = RunSpec(
configuration=conf,
Expand Down
9 changes: 6 additions & 3 deletions src/dstack/_internal/cli/services/configurators/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
ApplyEnvVarsConfiguratorMixin,
BaseApplyConfigurator,
)
from dstack._internal.cli.services.profile import apply_profile_args, register_profile_args
from dstack._internal.cli.services.profile import (
apply_profile_args,
load_profile_from_args,
register_profile_args,
)
from dstack._internal.cli.services.repos import (
get_repo_from_dir,
get_repo_from_url,
Expand Down Expand Up @@ -68,7 +72,6 @@
from dstack._internal.utils.nested_list import NestedList, NestedListItem
from dstack._internal.utils.path import is_absolute_posix_path
from dstack.api._public.runs import Run
from dstack.api.utils import load_profile

_KNOWN_AMD_GPUS = {gpu.name.lower() for gpu in gpuhunt.KNOWN_AMD_GPUS}
_KNOWN_NVIDIA_GPUS = {gpu.name.lower() for gpu in gpuhunt.KNOWN_NVIDIA_GPUS}
Expand Down Expand Up @@ -132,7 +135,7 @@ def get_plan(
repo = self.get_repo(conf, configuration_path, configurator_args)
if repo is None:
repo = init_default_virtual_repo(api=self.api)
profile = load_profile(Path.cwd(), configurator_args.profile)
profile = load_profile_from_args(args=configurator_args, repo_dir=Path.cwd())
with console.status("Getting apply plan..."):
run_plan = self.api.runs.get_run_plan(
configuration=conf,
Expand Down
42 changes: 39 additions & 3 deletions src/dstack/_internal/cli/services/profile.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import argparse
import os

from dstack._internal.core.errors import CLIError
from dstack._internal.core.models.profiles import (
CreationPolicy,
Profile,
ProfileParams,
ProfileRetry,
SpotPolicy,
parse_duration,
parse_max_duration,
)
from dstack._internal.utils.env import environ
from dstack._internal.utils.path import PathLike
from dstack.api.utils import load_profile

_PROFILE_ENV_VAR = "DSTACK_PROFILE"
_NO_PROFILE_ENV_VAR = "DSTACK_NO_PROFILE"


def register_profile_args(parser: argparse.ArgumentParser):
Expand All @@ -17,13 +25,22 @@ def register_profile_args(parser: argparse.ArgumentParser):
CLI arguments that override `profiles.yml` settings.
"""
profile_group = parser.add_argument_group("Profile")
profile_group.add_argument(
profile_exc = profile_group.add_mutually_exclusive_group()
profile_exc.add_argument(
"--profile",
metavar="NAME",
help="The name of the profile. Defaults to [code]$DSTACK_PROFILE[/]",
default=os.getenv("DSTACK_PROFILE"),
help=f"The name of the profile. Defaults to [code]${_PROFILE_ENV_VAR}[/]",
dest="profile",
)
profile_exc.add_argument(
"--no-profile",
help=(
"Don't load any profile."
f" Enabled by default if [code]${_NO_PROFILE_ENV_VAR}[/] is set and [code]--profile[/] is not specified"
),
action="store_true",
dest="no_profile",
)
profile_group.add_argument(
"--max-price",
metavar="PRICE",
Expand Down Expand Up @@ -131,6 +148,21 @@ def register_profile_args(parser: argparse.ArgumentParser):
)


def load_profile_from_args(args: argparse.Namespace, repo_dir: PathLike) -> Profile:
# precedence: --no-profile > --profile=name > DSTACK_NO_PROFILE=1 > DSTACK_PROFILE=name
if args.no_profile:
return _build_dummy_no_profile()
if args.profile is not None:
return load_profile(repo_dir=repo_dir, profile_name=args.profile)
try:
no_profile_from_env = environ.get_bool(_NO_PROFILE_ENV_VAR, default=False)
except ValueError as e:
raise CLIError(str(e)) from e
if no_profile_from_env:
return _build_dummy_no_profile()
return load_profile(repo_dir=repo_dir, profile_name=os.getenv(_PROFILE_ENV_VAR))


def apply_profile_args(
args: argparse.Namespace,
profile_settings: ProfileParams,
Expand Down Expand Up @@ -178,3 +210,7 @@ def max_duration(v: str) -> int:

def retry_duration(v: str) -> int:
return parse_duration(v)


def _build_dummy_no_profile() -> Profile:
return Profile(name="no-profile")
Loading