-
Notifications
You must be signed in to change notification settings - Fork 163
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement dstack apply for gateways (#1223)
* Implement basic dstack apply for gateways * Compare gateway configuration when running apply * Fix tests * Generate json schema for GatewayConfiguration * Add documentation on dstack apply and gateway configuration
- Loading branch information
Showing
38 changed files
with
527 additions
and
126 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# gateway | ||
|
||
The `gateway` configuration type allows creating and updating [gateways](../../concepts/services.md). | ||
|
||
!!! info "Filename" | ||
Configuration files must have a name ending with `.dstack.yml` (e.g., `.dstack.yml` or `serve.dstack.yml` are both acceptable) | ||
and can be located in the project's root directory or any nested folder. | ||
Any configuration can be applied via [`dstack apply`](../cli/index.md#dstack-apply). | ||
|
||
## Examples | ||
|
||
<div editor-title="gateway.dstack.yml"> | ||
|
||
```yaml | ||
type: gateway | ||
name: example-gateway | ||
backend: aws | ||
region: eu-west-1 | ||
domain: '*.example.com' | ||
``` | ||
</div> | ||
## Root reference | ||
#SCHEMA# dstack._internal.core.models.gateways.GatewayConfiguration | ||
overrides: | ||
show_root_heading: false | ||
type: | ||
required: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import argparse | ||
from pathlib import Path | ||
|
||
import yaml | ||
|
||
from dstack._internal.cli.commands import APIBaseCommand | ||
from dstack._internal.cli.services.configurators import ( | ||
get_apply_configurator_class, | ||
) | ||
from dstack._internal.cli.utils.common import cli_error | ||
from dstack._internal.core.errors import ConfigurationError | ||
from dstack._internal.core.models.configurations import ( | ||
AnyApplyConfiguration, | ||
parse_apply_configuration, | ||
) | ||
|
||
|
||
class ApplyCommand(APIBaseCommand): | ||
NAME = "apply" | ||
DESCRIPTION = "Apply dstack configuration" | ||
|
||
def _register(self): | ||
super()._register() | ||
self._parser.add_argument( | ||
"configuration_file", | ||
help="The path to the configuration file", | ||
) | ||
self._parser.add_argument( | ||
"--force", | ||
help="Force apply when no changes detected", | ||
action="store_true", | ||
) | ||
self._parser.add_argument( | ||
"-y", | ||
"--yes", | ||
help="Do not ask for confirmation", | ||
action="store_true", | ||
) | ||
|
||
def _command(self, args: argparse.Namespace): | ||
super()._command(args) | ||
try: | ||
configuration = _load_configuration(args.configuration_file) | ||
except ConfigurationError as e: | ||
raise cli_error(e) | ||
configurator_class = get_apply_configurator_class(configuration.type) | ||
configurator = configurator_class(api_client=self.api) | ||
configurator.apply_configuration(conf=configuration, args=args) | ||
|
||
|
||
def _load_configuration(configuration_file: str) -> AnyApplyConfiguration: | ||
configuration_path = Path(configuration_file) | ||
if not configuration_path.exists(): | ||
raise ConfigurationError(f"Configuration file {configuration_file} does not exist") | ||
try: | ||
with open(configuration_path, "r") as f: | ||
conf = parse_apply_configuration(yaml.safe_load(f)) | ||
except OSError: | ||
raise ConfigurationError(f"Failed to load configuration from {configuration_path}") | ||
return conf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from typing import Dict, Type | ||
|
||
from dstack._internal.cli.services.configurators.base import BaseApplyConfigurator | ||
from dstack._internal.cli.services.configurators.gateway import GatewayConfigurator | ||
from dstack._internal.core.models.configurations import ApplyConfigurationType | ||
|
||
apply_configurators_mapping: Dict[ApplyConfigurationType, Type[BaseApplyConfigurator]] = { | ||
cls.TYPE: cls for cls in [GatewayConfigurator] | ||
} | ||
|
||
|
||
def get_apply_configurator_class(configurator_type: str) -> Type[BaseApplyConfigurator]: | ||
return apply_configurators_mapping[ApplyConfigurationType(configurator_type)] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import argparse | ||
from abc import ABC, abstractmethod | ||
from typing import List | ||
|
||
from dstack._internal.core.models.configurations import ( | ||
AnyApplyConfiguration, | ||
ApplyConfigurationType, | ||
) | ||
from dstack.api._public import Client | ||
|
||
|
||
class BaseApplyConfigurator(ABC): | ||
TYPE: ApplyConfigurationType | ||
|
||
def __init__(self, api_client: Client): | ||
self.api_client = api_client | ||
|
||
@abstractmethod | ||
def apply_configuration(self, conf: AnyApplyConfiguration, args: argparse.Namespace): | ||
pass | ||
|
||
def register_args(self, parser: argparse.ArgumentParser): | ||
pass | ||
|
||
def apply_args( | ||
self, args: argparse.Namespace, unknown: List[str], conf: AnyApplyConfiguration | ||
): | ||
pass |
58 changes: 58 additions & 0 deletions
58
src/dstack/_internal/cli/services/configurators/gateway.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import argparse | ||
|
||
from dstack._internal.cli.services.configurators.base import BaseApplyConfigurator | ||
from dstack._internal.cli.utils.common import confirm_ask, console | ||
from dstack._internal.cli.utils.gateway import print_gateways_table | ||
from dstack._internal.core.errors import ResourceNotExistsError | ||
from dstack._internal.core.models.configurations import ApplyConfigurationType | ||
from dstack._internal.core.models.gateways import GatewayConfiguration | ||
|
||
|
||
class GatewayConfigurator(BaseApplyConfigurator): | ||
TYPE: ApplyConfigurationType = ApplyConfigurationType.GATEWAY | ||
|
||
def apply_configuration(self, conf: GatewayConfiguration, args: argparse.Namespace): | ||
# TODO: Show apply plan | ||
# TODO: Update gateway in-place when domain/default change | ||
confirmed = False | ||
if conf.name is not None: | ||
try: | ||
gateway = self.api_client.client.gateways.get( | ||
project_name=self.api_client.project, gateway_name=conf.name | ||
) | ||
except ResourceNotExistsError: | ||
pass | ||
else: | ||
if gateway.configuration == conf: | ||
if not args.force: | ||
console.print( | ||
"Gateway configuration has not changed. Use --force to recreate the gateway." | ||
) | ||
return | ||
if not args.yes and not confirm_ask( | ||
"Gateway configuration has not changed. Re-create the gateway?" | ||
): | ||
console.print("\nExiting...") | ||
return | ||
elif not args.yes and not confirm_ask( | ||
f"Gateway [code]{conf.name}[/] already exist. Re-create the gateway?" | ||
): | ||
console.print("\nExiting...") | ||
return | ||
confirmed = True | ||
with console.status("Deleting gateway..."): | ||
self.api_client.client.gateways.delete( | ||
project_name=self.api_client.project, gateways_names=[conf.name] | ||
) | ||
if not confirmed and not args.yes: | ||
if not confirm_ask( | ||
f"Gateway [code]{conf.name}[/] does not exist yet. Create the gateway?" | ||
): | ||
console.print("\nExiting...") | ||
return | ||
with console.status("Creating gateway..."): | ||
gateway = self.api_client.client.gateways.create( | ||
project_name=self.api_client.project, | ||
configuration=conf, | ||
) | ||
print_gateways_table([gateway]) |
Oops, something went wrong.