Skip to content

Commit 5b8ce35

Browse files
authored
Implement reading apply configuration from stdin (#2938)
* Support reading apply configurations from stdin * Never ask confirm with --yes
1 parent 83835ba commit 5b8ce35

File tree

6 files changed

+22
-7
lines changed

6 files changed

+22
-7
lines changed

src/dstack/_internal/cli/commands/apply.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import argparse
2-
from pathlib import Path
32

43
from argcomplete import FilesCompleter
54

65
from dstack._internal.cli.commands import APIBaseCommand
76
from dstack._internal.cli.services.configurators import (
7+
APPLY_STDIN_NAME,
88
get_apply_configurator_class,
99
load_apply_configuration,
1010
)
@@ -40,9 +40,12 @@ def _register(self):
4040
self._parser.add_argument(
4141
"-f",
4242
"--file",
43-
type=Path,
4443
metavar="FILE",
45-
help="The path to the configuration file. Defaults to [code]$PWD/.dstack.yml[/]",
44+
help=(
45+
"The path to the configuration file."
46+
" Specify [code]-[/] to read configuration from stdin."
47+
" Defaults to [code]$PWD/.dstack.yml[/]"
48+
),
4649
dest="configuration_file",
4750
).completer = FilesCompleter(allowednames=["*.yml", "*.yaml"])
4851
self._parser.add_argument(
@@ -104,6 +107,8 @@ def _command(self, args: argparse.Namespace):
104107
return
105108

106109
super()._command(args)
110+
if not args.yes and args.configuration_file == APPLY_STDIN_NAME:
111+
raise CLIError("Cannot read configuration from stdin if -y/--yes is not specified")
107112
if args.repo and args.no_repo:
108113
raise CLIError("Either --repo or --no-repo can be specified")
109114
repo = None

src/dstack/_internal/cli/services/configurators/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
from pathlib import Path
23
from typing import Dict, Optional, Tuple, Type
34

@@ -20,6 +21,9 @@
2021
parse_apply_configuration,
2122
)
2223

24+
APPLY_STDIN_NAME = "-"
25+
26+
2327
apply_configurators_mapping: Dict[ApplyConfigurationType, Type[BaseApplyConfigurator]] = {
2428
cls.TYPE: cls
2529
for cls in [
@@ -62,6 +66,8 @@ def load_apply_configuration(
6266
raise ConfigurationError(
6367
"No configuration file specified via `-f` and no default .dstack.yml configuration found"
6468
)
69+
elif configuration_file == APPLY_STDIN_NAME:
70+
configuration_path = sys.stdin.fileno()
6571
else:
6672
configuration_path = Path(configuration_file)
6773
if not configuration_path.exists():
@@ -71,4 +77,6 @@ def load_apply_configuration(
7177
conf = parse_apply_configuration(yaml.safe_load(f))
7278
except OSError:
7379
raise ConfigurationError(f"Failed to load configuration from {configuration_path}")
80+
if isinstance(configuration_path, int):
81+
return APPLY_STDIN_NAME, conf
7482
return str(configuration_path.absolute().relative_to(Path.cwd())), conf

src/dstack/_internal/cli/services/configurators/fleet.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def _apply_plan(self, plan: FleetPlan, command_args: argparse.Namespace):
151151
time.sleep(LIVE_TABLE_PROVISION_INTERVAL_SECS)
152152
fleet = self.api.client.fleets.get(self.api.project, fleet.name)
153153
except KeyboardInterrupt:
154-
if confirm_ask("Delete the fleet before exiting?"):
154+
if not command_args.yes and confirm_ask("Delete the fleet before exiting?"):
155155
with console.status("Deleting fleet..."):
156156
self.api.client.fleets.delete(
157157
project_name=self.api.project, names=[fleet.name]

src/dstack/_internal/cli/services/configurators/gateway.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def apply_configuration(
121121
time.sleep(LIVE_TABLE_PROVISION_INTERVAL_SECS)
122122
gateway = self.api.client.gateways.get(self.api.project, gateway.name)
123123
except KeyboardInterrupt:
124-
if confirm_ask("Delete the gateway before exiting?"):
124+
if not command_args.yes and confirm_ask("Delete the gateway before exiting?"):
125125
with console.status("Deleting gateway..."):
126126
self.api.client.gateways.delete(
127127
project_name=self.api.project,

src/dstack/_internal/cli/services/configurators/run.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,9 @@ def apply_configuration(
218218
exit(1)
219219
except KeyboardInterrupt:
220220
try:
221-
if not confirm_ask(f"\nStop the run [code]{run.name}[/] before detaching?"):
221+
if command_args.yes or not confirm_ask(
222+
f"\nStop the run [code]{run.name}[/] before detaching?"
223+
):
222224
console.print("Detached")
223225
abort_at_exit = False
224226
return

src/dstack/_internal/cli/services/configurators/volume.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def apply_configuration(
110110
time.sleep(LIVE_TABLE_PROVISION_INTERVAL_SECS)
111111
volume = self.api.client.volumes.get(self.api.project, volume.name)
112112
except KeyboardInterrupt:
113-
if confirm_ask("Delete the volume before exiting?"):
113+
if not command_args.yes and confirm_ask("Delete the volume before exiting?"):
114114
with console.status("Deleting volume..."):
115115
self.api.client.volumes.delete(
116116
project_name=self.api.project, names=[volume.name]

0 commit comments

Comments
 (0)