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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ To point the CLI to the `dstack` server, configure it
with the server address, user token, and project name:

```shell
$ dstack config \
$ dstack project add \
--name main \
--url http://127.0.0.1:3000 \
--project main \
--token bbae0f28-d3dd-4820-bf61-8f4bb40815da

Configuration is updated at ~/.dstack/config.yml
Expand Down
4 changes: 2 additions & 2 deletions docker/server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ with the server address, user token, and project name:

```shell
$ pip install dstack
$ dstack config --url http://127.0.0.1:3000 \
--project main \
$ dstack project add --name main \
--url http://127.0.0.1:3000 \
--token bbae0f28-d3dd-4820-bf61-8f4bb40815da

Configuration is updated at ~/.dstack/config.yml
Expand Down
4 changes: 2 additions & 2 deletions docs/blog/posts/dstack-sky.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ set up with `dstack Sky`.
<div class="termy">

```shell
$ dstack config --url https://sky.dstack.ai \
--project my-awesome-project \
$ dstack project add --name my-awesome-project \
--url https://sky.dstack.ai \
--token ca1ee60b-7b3f-8943-9a25-6974c50efa75
```

Expand Down
4 changes: 2 additions & 2 deletions docs/docs/concepts/projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ and when using the CLI or API.

### Setting up the CLI

To use the CLI with a specific project, run the `dstack config` command with the server address, user token, and project name.
You can configure multiple projects on the client and set the default project using the [`dstack project`](../reference/cli/dstack/project.md) CLI command.

You can find the command on the project’s settings page:

<img src="https://dstack.ai/static-assets/static-assets/images/dstack-projects-project-cli.png" width="750px" />
<img src="https://dstack.ai/static-assets/static-assets/images/dstack-projects-project-cli-v2.png" width="750px" />

??? info "API"
In addition to the UI, managing projects, users, and user permissions can also be done via the [REST API](../reference/api/rest/index.md).
4 changes: 2 additions & 2 deletions docs/docs/guides/dstack-sky.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ Then, install the CLI on your machine and use the copied command.

```shell
$ pip install dstack
$ dstack config --url https://sky.dstack.ai \
--project peterschmidt85 \
$ dstack project add --name peterschmidt85 \
--url https://sky.dstack.ai \
--token bbae0f28-d3dd-4820-bf61-8f4bb40815da

Configuration is updated at ~/.dstack/config.yml
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/installation/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ with the server address, user token, and project name:
<div class="termy">

```shell
$ dstack config \
$ dstack project add \
--name main \
--url http://127.0.0.1:3000 \
--project main \
--token bbae0f28-d3dd-4820-bf61-8f4bb40815da

Configuration is updated at ~/.dstack/config.yml
Expand Down
3 changes: 3 additions & 0 deletions docs/docs/reference/cli/dstack/config.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# 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`.

Expand Down
63 changes: 63 additions & 0 deletions docs/docs/reference/cli/dstack/project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# dstack project

Before the CLI can be used, it must be configured with a [project](../../../concepts/projects.md), including a project name, server address, and user token. You can configure multiple projects using the `dstack project` CLI command. The configuration is stored in `~/.dstack/config.yml`.

> The `dstack server` command automatically creates the default `main` project and adds its configuration in `~/.dstack/config.yml`.

The `dstack project set-default` command can be used to switch between multiple projects.

## dstack project add

This command adds a new project configuration.

<div class="termy">

```shell
$ dstack project add --help
#GENERATE#
```

</div>

You can find the command on the project’s settings page:

<img src="https://dstack.ai/static-assets/static-assets/images/dstack-projects-project-cli-v2.png" width="750px" />

## dstack project list

This command lists the projects configured on the client.

<div class="termy">

```shell
$ dstack project list --help
#GENERATE#
```

</div>

## dstack project set-default

This command sets the given project as default.

<div class="termy">

```shell
$ dstack project set-default --help
#GENERATE#
```

</div>

## dstack project delete

This command deletes the given project configuration.

<div class="termy">

```shell
$ dstack project delete --help
#GENERATE#
```

</div>
2 changes: 1 addition & 1 deletion frontend/src/layouts/AppLayout/TutorialPanel/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const CONFIGURE_CLI_TUTORIAL: TutorialPanelProps.Tutorial = {
title: 'Configure the CLI',
steps: [
{
title: 'Run the dstack config command',
title: 'Run the dstack project add command',
content: 'Run this command on your local machine to configure the dstack CLI.',
hotspotId: HotspotIds.CONFIGURE_CLI_COMMAND,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type Args = {
export const useConfigProjectCliCommand = ({ projectName }: Args) => {
const currentUserToken = useAppSelector(selectAuthToken);

const cliCommand = `dstack config --url ${location.origin} --project ${projectName} --token ${currentUserToken}`;
const cliCommand = `dstack project add --name ${projectName} --url ${location.origin} --token ${currentUserToken}`;

const copyCliCommand = () => {
copyToClipboard(cliCommand);
Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ nav:
- dstack attach: docs/reference/cli/dstack/attach.md
- dstack logs: docs/reference/cli/dstack/logs.md
- dstack metrics: docs/reference/cli/dstack/metrics.md
- dstack config: docs/reference/cli/dstack/config.md
- dstack project: docs/reference/cli/dstack/project.md
- dstack fleet: docs/reference/cli/dstack/fleet.md
- dstack offer: docs/reference/cli/dstack/offer.md
- dstack volume: docs/reference/cli/dstack/volume.md
Expand Down
2 changes: 1 addition & 1 deletion src/dstack/_internal/cli/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

class ConfigCommand(BaseCommand):
NAME = "config"
DESCRIPTION = "Configure CLI"
DESCRIPTION = "Configure CLI (deprecated; use `dstack project`)"

def _register(self):
super()._register()
Expand Down
161 changes: 161 additions & 0 deletions src/dstack/_internal/cli/commands/project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import argparse

from requests import HTTPError
from rich.table import Table

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 ClientError, CLIError
from dstack._internal.core.services.configs import ConfigManager
from dstack._internal.utils.logging import get_logger

logger = get_logger(__name__)


class ProjectCommand(BaseCommand):
NAME = "project"
DESCRIPTION = "Manage projects configs"

def _register(self):
super()._register()
subparsers = self._parser.add_subparsers(dest="subcommand", help="Command to execute")

# Add subcommand
add_parser = subparsers.add_parser("add", help="Add or update a project config")
add_parser.add_argument(
"--name", type=str, help="The name of the project to configure", required=True
)
add_parser.add_argument("--url", type=str, help="Server url", required=True)
add_parser.add_argument("--token", type=str, help="User token", required=True)
add_parser.add_argument(
"-y",
"--yes",
help="Don't ask for confirmation (e.g. update the config)",
action="store_true",
)
add_parser.add_argument(
"-n",
"--no",
help="Don't ask for confirmation (e.g. do not update the config)",
action="store_true",
)
add_parser.set_defaults(subfunc=self._add)

# Delete subcommand
delete_parser = subparsers.add_parser("delete", help="Delete a project config")
delete_parser.add_argument(
"--name", type=str, help="The name of the project to delete", required=True
)
delete_parser.add_argument(
"-y",
"--yes",
help="Don't ask for confirmation",
action="store_true",
)
delete_parser.set_defaults(subfunc=self._delete)

# List subcommand
list_parser = subparsers.add_parser("list", help="List configured projects")
list_parser.set_defaults(subfunc=self._list)

# Set default subcommand
set_default_parser = subparsers.add_parser("set-default", help="Set default project")
set_default_parser.add_argument(
"name", type=str, help="The name of the project to set as default"
)
set_default_parser.set_defaults(subfunc=self._set_default)

def _command(self, args: argparse.Namespace):
if not hasattr(args, "subfunc"):
args.subfunc = self._list
args.subfunc(args)

def _add(self, args: argparse.Namespace):
config_manager = ConfigManager()
api_client = dstack.api.server.APIClient(base_url=args.url, token=args.token)
try:
api_client.projects.get(args.name)
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.name}' not found.")
else:
raise e
default_project = config_manager.get_project_config()
if (
default_project is None
or default_project.name != args.name
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.name}' as your default project?")
)
if not args.no
else False
)
config_manager.configure_project(
name=args.name, 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}
)

def _delete(self, args: argparse.Namespace):
config_manager = ConfigManager()
if args.yes or confirm_ask(f"Are you sure you want to delete project '{args.name}'?"):
config_manager.delete_project(args.name)
config_manager.save()
console.print("[grey58]OK[/]")

def _list(self, args: argparse.Namespace):
config_manager = ConfigManager()
default_project = config_manager.get_project_config()

table = Table(box=None)
table.add_column("PROJECT", style="bold", no_wrap=True)
table.add_column("URL", style="grey58")
table.add_column("USER", style="grey58")
table.add_column("DEFAULT", justify="center")

for project_name in config_manager.list_projects():
project_config = config_manager.get_project_config(project_name)
is_default = project_name == default_project.name if default_project else False

# Get username from API
try:
api_client = dstack.api.server.APIClient(
base_url=project_config.url, token=project_config.token
)
user_info = api_client.users.get_my_user()
username = user_info.username
except ClientError:
username = "(invalid token)"

table.add_row(
project_name,
project_config.url,
username,
"✓" if is_default else "",
style="bold" if is_default else None,
)

console.print(table)

def _set_default(self, args: argparse.Namespace):
config_manager = ConfigManager()
project_config = config_manager.get_project_config(args.name)
if project_config is None:
raise CLIError(f"Project '{args.name}' not found")

config_manager.configure_project(
name=args.name, url=project_config.url, token=project_config.token, default=True
)
config_manager.save()
console.print("[grey58]OK[/]")
2 changes: 2 additions & 0 deletions src/dstack/_internal/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from dstack._internal.cli.commands.logs import LogsCommand
from dstack._internal.cli.commands.metrics import MetricsCommand
from dstack._internal.cli.commands.offer import OfferCommand
from dstack._internal.cli.commands.project import ProjectCommand
from dstack._internal.cli.commands.ps import PsCommand
from dstack._internal.cli.commands.server import ServerCommand
from dstack._internal.cli.commands.stats import StatsCommand
Expand Down Expand Up @@ -69,6 +70,7 @@ def main():
OfferCommand.register(subparsers)
LogsCommand.register(subparsers)
MetricsCommand.register(subparsers)
ProjectCommand.register(subparsers)
PsCommand.register(subparsers)
ServerCommand.register(subparsers)
StatsCommand.register(subparsers)
Expand Down
Loading