diff --git a/docs/docs/guides/dstack-sky.md b/docs/docs/guides/dstack-sky.md
index a51054f70b..e12f1ccef3 100644
--- a/docs/docs/guides/dstack-sky.md
+++ b/docs/docs/guides/dstack-sky.md
@@ -5,7 +5,7 @@ sign up with [dstack Sky](../guides/dstack-sky.md).
### Set up the CLI
-If you've signed up, open your project settings, and copy the `dstack config` command to point the CLI to the project.
+If you've signed up, open your project settings, and copy the `dstack project add` command to point the CLI to the project.
{ width=800 }
diff --git a/docs/docs/reference/cli/dstack/config.md b/docs/docs/reference/cli/dstack/config.md
deleted file mode 100644
index 1dad45730c..0000000000
--- a/docs/docs/reference/cli/dstack/config.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# dstack config
-
-!!! info "Deprecated"
- The `dstack config` is deprecated. Use [`dstack project`](project.md) instead.
-
-Both the CLI and API need to be configured with the server address, user token, and project name
-via `~/.dstack/config.yml`.
-
-At startup, the server automatically configures CLI and API with the server address, user token, and
-the default project name (`main`). This configuration is stored via `~/.dstack/config.yml`.
-
-To use CLI and API on different machines or projects, use the `dstack config` command.
-
-## Usage
-
-
-
-```shell
-$ dstack config --help
-#GENERATE#
-```
-
-
-
-[//]: # (TODO: Provide examples)
diff --git a/src/dstack/_internal/cli/commands/config.py b/src/dstack/_internal/cli/commands/config.py
deleted file mode 100644
index adff5c8709..0000000000
--- a/src/dstack/_internal/cli/commands/config.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import argparse
-
-from requests import HTTPError
-
-import dstack.api.server
-from dstack._internal.cli.commands import BaseCommand
-from dstack._internal.cli.utils.common import confirm_ask, console
-from dstack._internal.core.errors import CLIError
-from dstack._internal.core.services.configs import ConfigManager
-from dstack._internal.utils.logging import get_logger
-
-logger = get_logger(__name__)
-
-
-class ConfigCommand(BaseCommand):
- NAME = "config"
- DESCRIPTION = "Configure CLI (deprecated; use `dstack project`)"
-
- def _register(self):
- super()._register()
- self._parser.add_argument(
- "--project", type=str, help="The name of the project to configure"
- )
- self._parser.add_argument("--url", type=str, help="Server url")
- self._parser.add_argument("--token", type=str, help="User token")
- self._parser.add_argument(
- "-y",
- "--yes",
- help="Don't ask for confirmation (e.g. update the config)",
- action="store_true",
- )
- self._parser.add_argument(
- "--remove", action="store_true", help="Delete project configuration"
- )
- self._parser.add_argument(
- "-n",
- "--no",
- help="Don't ask for confirmation (e.g. do not update the config)",
- action="store_true",
- )
-
- def _command(self, args: argparse.Namespace):
- super()._command(args)
- config_manager = ConfigManager()
- if args.remove:
- config_manager.delete_project(args.project)
- config_manager.save()
- console.print("[grey58]OK[/]")
- return
-
- if not args.url:
- console.print("Specify --url")
- exit(1)
- elif not args.token:
- console.print("Specify --token")
- exit(1)
- api_client = dstack.api.server.APIClient(base_url=args.url, token=args.token)
- try:
- api_client.projects.get(args.project)
- except HTTPError as e:
- if e.response.status_code == 403:
- raise CLIError("Forbidden. Ensure the token is valid.")
- elif e.response.status_code == 404:
- raise CLIError(f"Project '{args.project}' not found.")
- else:
- raise e
- default_project = config_manager.get_project_config()
- if (
- default_project is None
- or default_project.name != args.project
- or default_project.url != args.url
- or default_project.token != args.token
- ):
- set_it_as_default = (
- (
- args.yes
- or not default_project
- or confirm_ask(f"Set '{args.project}' as your default project?")
- )
- if not args.no
- else False
- )
- config_manager.configure_project(
- name=args.project, url=args.url, token=args.token, default=set_it_as_default
- )
- config_manager.save()
- logger.info(
- f"Configuration updated at {config_manager.config_filepath}", {"show_path": False}
- )
diff --git a/src/dstack/_internal/cli/commands/gateway.py b/src/dstack/_internal/cli/commands/gateway.py
index 384feaba63..31ecef3ddf 100644
--- a/src/dstack/_internal/cli/commands/gateway.py
+++ b/src/dstack/_internal/cli/commands/gateway.py
@@ -17,8 +17,6 @@
print_gateways_table,
)
from dstack._internal.core.errors import CLIError
-from dstack._internal.core.models.backends.base import BackendType
-from dstack._internal.core.models.gateways import GatewayConfiguration
from dstack._internal.utils.logging import get_logger
logger = get_logger(__name__)
@@ -62,24 +60,6 @@ def _register(self):
help="Output in JSON format (equivalent to --format json)",
)
- create_parser = subparsers.add_parser(
- "create",
- help="Add a gateway. Deprecated in favor of `dstack apply` with gateway configuration.",
- formatter_class=self._parser.formatter_class,
- )
- create_parser.set_defaults(subfunc=self._create)
- create_parser.add_argument(
- "--backend", choices=["aws", "azure", "gcp", "kubernetes"], required=True
- )
- create_parser.add_argument("--region", required=True)
- create_parser.add_argument(
- "--set-default", action="store_true", help="Set as default gateway for the project"
- )
- create_parser.add_argument("--name", help="Set a custom name for the gateway")
- create_parser.add_argument(
- "--domain", help="Set the domain for the gateway", required=True
- )
-
delete_parser = subparsers.add_parser(
"delete", help="Delete a gateway", formatter_class=self._parser.formatter_class
)
@@ -129,26 +109,6 @@ def _list(self, args: argparse.Namespace):
except KeyboardInterrupt:
pass
- def _create(self, args: argparse.Namespace):
- logger.warning(
- "`dstack gateway create` is deperecated in favor of `dstack apply` with gateway configurations."
- )
- with console.status("Creating gateway..."):
- configuration = GatewayConfiguration(
- name=args.name,
- backend=BackendType(args.backend),
- region=args.region,
- )
- gateway = self.api.client.gateways.create(self.api.project, configuration)
- if args.set_default:
- self.api.client.gateways.set_default(self.api.project, gateway.name)
- if args.domain:
- self.api.client.gateways.set_wildcard_domain(
- self.api.project, gateway.name, args.domain
- )
- gateway = self.api.client.gateways.get(self.api.project, gateway.name)
- print_gateways_table([gateway])
-
def _delete(self, args: argparse.Namespace):
gateway = self.api.client.gateways.get(self.api.project, args.name)
print_gateways_table([gateway])
diff --git a/src/dstack/_internal/cli/commands/stats.py b/src/dstack/_internal/cli/commands/stats.py
deleted file mode 100644
index 66acae7d04..0000000000
--- a/src/dstack/_internal/cli/commands/stats.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import argparse
-
-from dstack._internal.cli.commands.metrics import MetricsCommand
-from dstack._internal.utils.logging import get_logger
-
-logger = get_logger(__name__)
-
-
-class StatsCommand(MetricsCommand):
- NAME = "stats"
-
- def _command(self, args: argparse.Namespace):
- logger.warning("`dstack stats` is deprecated in favor of `dstack metrics`")
- super()._command(args)
diff --git a/src/dstack/_internal/cli/main.py b/src/dstack/_internal/cli/main.py
index 2ef2979051..55248f3cf0 100644
--- a/src/dstack/_internal/cli/main.py
+++ b/src/dstack/_internal/cli/main.py
@@ -7,7 +7,6 @@
from dstack._internal.cli.commands.apply import ApplyCommand
from dstack._internal.cli.commands.attach import AttachCommand
from dstack._internal.cli.commands.completion import CompletionCommand
-from dstack._internal.cli.commands.config import ConfigCommand
from dstack._internal.cli.commands.delete import DeleteCommand
from dstack._internal.cli.commands.fleet import FleetCommand
from dstack._internal.cli.commands.gateway import GatewayCommand
@@ -19,7 +18,6 @@
from dstack._internal.cli.commands.ps import PsCommand
from dstack._internal.cli.commands.secrets import SecretCommand
from dstack._internal.cli.commands.server import ServerCommand
-from dstack._internal.cli.commands.stats import StatsCommand
from dstack._internal.cli.commands.stop import StopCommand
from dstack._internal.cli.commands.volume import VolumeCommand
from dstack._internal.cli.utils.common import _colors, console
@@ -63,7 +61,6 @@ def main():
subparsers = parser.add_subparsers(metavar="COMMAND")
ApplyCommand.register(subparsers)
AttachCommand.register(subparsers)
- ConfigCommand.register(subparsers)
DeleteCommand.register(subparsers)
FleetCommand.register(subparsers)
GatewayCommand.register(subparsers)
@@ -75,7 +72,6 @@ def main():
PsCommand.register(subparsers)
SecretCommand.register(subparsers)
ServerCommand.register(subparsers)
- StatsCommand.register(subparsers)
StopCommand.register(subparsers)
VolumeCommand.register(subparsers)
CompletionCommand.register(subparsers)
diff --git a/src/dstack/_internal/core/models/profiles.py b/src/dstack/_internal/core/models/profiles.py
index 896049ffa6..f69f094028 100644
--- a/src/dstack/_internal/core/models/profiles.py
+++ b/src/dstack/_internal/core/models/profiles.py
@@ -98,26 +98,6 @@ def parse_idle_duration(v: Optional[Union[int, str, bool]]) -> Optional[int]:
return parse_duration(v)
-# Deprecated in favor of ProfileRetry().
-# TODO: Remove when no longer referenced.
-class ProfileRetryPolicy(CoreModel):
- retry: Annotated[bool, Field(description="Whether to retry the run on failure or not")] = False
- duration: Annotated[
- Optional[Union[int, str]],
- Field(description="The maximum period of retrying the run, e.g., `4h` or `1d`"),
- ] = None
-
- _validate_duration = validator("duration", pre=True, allow_reuse=True)(parse_duration)
-
- @root_validator
- def _validate_fields(cls, values):
- if values["retry"] and "duration" not in values:
- values["duration"] = DEFAULT_RETRY_DURATION
- if values.get("duration") is not None:
- values["retry"] = True
- return values
-
-
class RetryEvent(str, Enum):
NO_CAPACITY = "no-capacity"
INTERRUPTION = "interruption"
diff --git a/src/tests/_internal/cli/commands/test_config.py b/src/tests/_internal/cli/commands/test_config.py
deleted file mode 100644
index 430e51f8f5..0000000000
--- a/src/tests/_internal/cli/commands/test_config.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from pathlib import Path
-from unittest.mock import patch
-
-import yaml
-from pytest import CaptureFixture
-
-from dstack._internal.utils.logging import get_logger
-from tests._internal.cli.common import run_dstack_cli
-
-
-class TestConfig:
- def test_configures_project(self, capsys: CaptureFixture, tmp_path: Path):
- cli_config_path = tmp_path / ".dstack" / "config.yml"
- logger = get_logger("dstack._internal.cli.commands.config")
- with patch.object(logger, "info") as logger_info_mock:
- with patch("dstack.api.server.APIClient") as APIClientMock:
- api_client_mock = APIClientMock.return_value
- api_client_mock.projects.get
- exit_code = run_dstack_cli(
- [
- "config",
- "--url",
- "http://127.0.0.1:31313",
- "--project",
- "project",
- "--token",
- "token",
- ],
- home_dir=tmp_path,
- )
- APIClientMock.assert_called_once_with(
- base_url="http://127.0.0.1:31313", token="token"
- )
- logger_info_mock.assert_called_once_with(
- f"Configuration updated at {cli_config_path}", {"show_path": False}
- )
- assert exit_code == 0
- assert yaml.load(cli_config_path.read_text(), yaml.FullLoader) == {
- "projects": [
- {
- "default": True,
- "name": "project",
- "token": "token",
- "url": "http://127.0.0.1:31313",
- }
- ],
- "repos": [],
- }
diff --git a/src/tests/_internal/cli/commands/test_project.py b/src/tests/_internal/cli/commands/test_project.py
new file mode 100644
index 0000000000..1572e25740
--- /dev/null
+++ b/src/tests/_internal/cli/commands/test_project.py
@@ -0,0 +1,42 @@
+from pathlib import Path
+from unittest.mock import patch
+
+import yaml
+from pytest import CaptureFixture
+
+from tests._internal.cli.common import run_dstack_cli
+
+
+class TestProjectAdd:
+ def test_adds_project(self, capsys: CaptureFixture, tmp_path: Path):
+ cli_config_path = tmp_path / ".dstack" / "config.yml"
+ with patch("dstack.api.server.APIClient") as APIClientMock:
+ api_client_mock = APIClientMock.return_value
+ exit_code = run_dstack_cli(
+ [
+ "project",
+ "add",
+ "--name",
+ "project",
+ "--url",
+ "http://127.0.0.1:31313",
+ "--token",
+ "token",
+ "-y",
+ ],
+ home_dir=tmp_path,
+ )
+ APIClientMock.assert_called_once_with(base_url="http://127.0.0.1:31313", token="token")
+ api_client_mock.projects.get.assert_called_with("project")
+ assert exit_code == 0
+ assert yaml.load(cli_config_path.read_text(), yaml.FullLoader) == {
+ "projects": [
+ {
+ "default": True,
+ "name": "project",
+ "token": "token",
+ "url": "http://127.0.0.1:31313",
+ }
+ ],
+ "repos": [],
+ }