Skip to content
Open
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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,14 @@ The local CLI dependencies are managed by `uv` from [pyproject.toml](pyproject.t

## Generated Files

The Kubernetes wizard always writes artifacts to `kubernetes/aws/artifacts/` (relative to the repo root), regardless of the directory you ran the CLI from:
The Kubernetes wizard writes each deployment's artifacts into its own subfolder under `kubernetes/aws/artifacts/<folder-name>/` (defaulting to the cluster name). Per folder:

- `cluster-config.yaml` — Deepgram-style `eksctl` cluster config
- `eksctl-expanded-cluster-config.yaml` — optional expanded `eksctl --dry-run` output
- `my-values.yaml` — Helm values rendered after EFS provisioning
- `session.yaml` — default save path when the wizard runs without `--config`
- `helm-values.yaml` — Helm values rendered after EFS provisioning
- `session.yaml` — wizard's saved config (used for `--config X` re-runs)

When the wizard finishes, it prompts for the folder name; if the folder already exists, it asks for confirmation before overwriting.

The Docker path writes its compose/TOML output into `$HOME/deepgram-self-hosted/` on the target host.

Expand Down
50 changes: 50 additions & 0 deletions deployment.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Self-hosting Deepgram services involves a few pieces of authentication,
running several container images, and exposing necessary config files and models.

Our documentation includes a series of guides that will take you through the steps of
provisioning hardware, configuring your deployment environment, generating credentials,
deploying Deepgram services, and maintaining and scaling your environment.
The series of guides start at this link:
https://developers.deepgram.com/docs/self-hosted-introduction

In the guides, you will be prompted to download models into a models directory. If you already
have an existing self-hosted environment, you can download these newly provided models alongside
your currently deployed models.

These model files are available for download from Amazon S3 at the following links.
Files:
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/nova-3-general.en.streaming.40bd3654.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/phoneme.es.4a2fc36c.dg
https://deepgram-onprem.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/profanity-filter.a70ceacc.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/diarizer.streaming.6ff6f59c.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/nova-3-general.en.batch.2187e11a.dg
https://deepgram-onprem.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/diarizer.batch.a9f85c2b.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/end-of-turn.4e9dbd96.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/flux-general-en.caf79279.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/aura-2.voice-pack.es.c053c7a8.dg
https://deepgram-onprem.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/phoneme.en.824ec1be.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/aura-2.generator.es.04355c1e.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/aura-2.generator.en.bdb6b6f3.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/nova-3-general.multi.streaming.421ebff2.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/nova-3-medical.multi.batch.58bc98aa.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/nova-3-general.es.streaming.509be9b5.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/entity-detector.batch.06bc8f36.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/aura-2.voice-pack.en.75d5ec84.dg
https://deepgram-onprem.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/sit.80ab3179.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/flux-general-multi.19857d9c.dg
https://deepgram-onprem.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/summarizerv2.67875a7f.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/nova-3-general.multi.batch.b6e78fa4.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/nova-3-medical.en.streaming.f70581e5.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/nova-3-general.es.batch.cb233499.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/entity-detector.en.streaming.90424f3a.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/nova-3-medical.en.batch.91d566f5.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/g2p.89555db3.dg
https://deepgram-self-hosted.s3.us-east-2.amazonaws.com/35fd69d8-b57a-49eb-9ab1-42fc8fe9fb32/models/language-detector.524a9dcb.dg

To utilize all models/features in this deployment, you must upgrade your Deepgram product images to the `260430` self-hosted release or higher.
See the Deepgram Changelog (https://deepgram.com/changelog) for a list of all releases (filter by "Self-Hosted").



To support entity detection, you will need to update your API configuration files.
See https://deepgram.gitbook.io/help-center/self-hosted/how-can-i-enable-entity-detection-in-my-self-hosted-deployment#updating-your-self-hosted-deployment
27 changes: 14 additions & 13 deletions kubernetes/aws/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ Interactive wizard for running Deepgram self-hosted services on AWS EKS, driven
## What This Workflow Does

- Collects deployment options interactively (or loads a saved YAML config)
- Renders a Deepgram-style `eksctl` cluster config to `artifacts/cluster-config.yaml`
- Renders a Deepgram-style `eksctl` cluster config to `artifacts/<folder>/cluster-config.yaml`
- Optionally creates the EKS cluster via `eksctl create cluster`
- Creates or reuses encrypted EFS storage, ensures NFS ingress, and creates missing mount targets per AZ
- Installs the EFS CSI driver addon using the IAM role that `eksctl` provisioned
- Creates the `dg-self-hosted` namespace
- Creates Kubernetes secrets in-cluster (only if you opt in; credentials are not written to disk)
- Renders Helm values to `artifacts/my-values.yaml`
- Renders Helm values to `artifacts/<folder>/helm-values.yaml`
- Installs or upgrades the Deepgram self-hosted Helm chart

## Requirements
Expand Down Expand Up @@ -66,8 +66,8 @@ After the wizard collects answers, the summary screen renders three Rich tables

Before deploy, the (possibly edited) config is written to disk:

- With `--config X`, back to the same path.
- Without `--config`, the wizard prompts for a save path defaulting to `kubernetes/aws/artifacts/session.yaml`.
- With `--config X`, back to the same path. Rendered `cluster-config.yaml` and `helm-values.yaml` land next to it (in `Path(X).parent`).
- Without `--config`, the wizard asks for a folder name (defaulting to the cluster name) and creates `kubernetes/aws/artifacts/<folder>/` with `session.yaml`, `cluster-config.yaml`, and `helm-values.yaml` inside. If the folder already exists with files in it, you'll be asked to confirm before overwriting.

Every saved config is written with file mode `0600` (owner-only).

Expand All @@ -78,11 +78,12 @@ Two ways to enable it:
1. From the wizard, answer **yes** to `Dry run (write artifacts only, do not provision)?`. The summary's primary action becomes **Render artifacts (dry run)**.
2. From a saved config, set `actions.dry_run: true` and run `setup kubernetes aws --config X`.

Generated files are written under `kubernetes/aws/artifacts/`:
Generated files are written under `kubernetes/aws/artifacts/<folder>/`:

- `artifacts/cluster-config.yaml` — Deepgram-style `eksctl` cluster config
- `artifacts/eksctl-expanded-cluster-config.yaml` — optional expanded `eksctl --dry-run` output (set `actions.expanded_eksctl_dry_run: true` in the config to write this)
- `artifacts/my-values.yaml` — Helm values, only written after EFS provisioning succeeds (so it's absent in dry-run mode)
- `cluster-config.yaml` — Deepgram-style `eksctl` cluster config
- `eksctl-expanded-cluster-config.yaml` — optional expanded `eksctl --dry-run` output (set `actions.expanded_eksctl_dry_run: true` in the config to write this)
- `helm-values.yaml` — Helm values, only written after EFS provisioning succeeds (so it's absent in dry-run mode)
- `session.yaml` — the wizard's saved config (driver YAML for re-runs)

## Full Deployment

Expand All @@ -92,7 +93,7 @@ Equivalent Helm command if you want to apply the rendered values manually:

```bash
helm install deepgram deepgram/deepgram-self-hosted \
-f artifacts/my-values.yaml \
-f artifacts/<folder>/helm-values.yaml \
--namespace dg-self-hosted \
--atomic \
--timeout 1h
Expand Down Expand Up @@ -131,14 +132,14 @@ Resolution order at deploy time: in-memory wizard input → env vars → values
### Dry run shows extra fields

Symptom:
- `artifacts/eksctl-expanded-cluster-config.yaml` contains many defaults that are not present in Deepgram's sample config
- `artifacts/<folder>/eksctl-expanded-cluster-config.yaml` contains many defaults that are not present in Deepgram's sample config

Cause:
- `eksctl create cluster --dry-run` normalizes and expands defaults. This is expected.

Fix:
- Compare Deepgram-style input against `artifacts/cluster-config.yaml`
- Use `artifacts/eksctl-expanded-cluster-config.yaml` only to inspect what `eksctl` will derive internally
- Compare Deepgram-style input against `artifacts/<folder>/cluster-config.yaml`
- Use `artifacts/<folder>/eksctl-expanded-cluster-config.yaml` only to inspect what `eksctl` will derive internally

### EFS CSI addon role missing

Expand All @@ -147,7 +148,7 @@ Symptom:
- Error mentions an EFS CSI IAM role

Checks:
- The cluster was created from `artifacts/cluster-config.yaml`
- The cluster was created from `artifacts/<folder>/cluster-config.yaml`
- `eksctl` successfully created IAM service accounts
- AWS IAM contains the cluster-scoped EFS CSI role printed in the script summary, for example `<cluster-name>-efs-csi-driver-role`

Expand Down
23 changes: 20 additions & 3 deletions src/deepgram_self_hosted/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
from deepgram_self_hosted.config import (
clone_eks_config,
default_eks_config,
get_path,
load_config,
write_config,
)
from deepgram_self_hosted.paths import artifacts_dir_for
from deepgram_self_hosted.providers import docker_aws, kubernetes_aws, kubernetes_aws_workflow

console = Console()
Expand Down Expand Up @@ -107,9 +109,14 @@ def plan_kubernetes_aws(
),
],
output_dir: Annotated[
Path,
typer.Option("--output-dir", "-o", help="Directory for rendered artifacts."),
] = Path("kubernetes/aws/artifacts"),
Path | None,
typer.Option(
"--output-dir",
"-o",
help="Directory for rendered artifacts. "
"Defaults to kubernetes/aws/artifacts/<cluster-name>/.",
),
] = None,
resolve_aws: Annotated[
bool,
typer.Option(
Expand All @@ -119,6 +126,16 @@ def plan_kubernetes_aws(
] = False,
) -> None:
"""Render EKS cluster config and Helm values from Python without applying them."""
if output_dir is None:
cluster_name = str(
get_path(
load_config(config),
"cluster",
"name",
default="deepgram-self-hosted-cluster",
)
)
output_dir = artifacts_dir_for(cluster_name)
Comment on lines +129 to +138
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR BUG Move output_dir derivation inside the try block to catch load_config errors

load_config(config) at line 132 is outside the try/except (RuntimeError, ValueError) block. When --output-dir is omitted (the new default) and the config is not a YAML mapping, ValueError escapes unhandled as a raw traceback instead of printing [red]Could not render Kubernetes/AWS plan:[/red].

Suggested change
if output_dir is None:
cluster_name = str(
get_path(
load_config(config),
"cluster",
"name",
default="deepgram-self-hosted-cluster",
)
)
output_dir = artifacts_dir_for(cluster_name)
try:
if output_dir is None:
cluster_name = str(
get_path(
load_config(config),
"cluster",
"name",
default="deepgram-self-hosted-cluster",
)
)
output_dir = artifacts_dir_for(cluster_name)
Prompt to fix with AI

Copy this prompt into your AI coding assistant to fix this issue.

In src/deepgram_self_hosted/cli.py, the `if output_dir is None:` block (lines 129–138) that calls `load_config(config)` sits BEFORE the `try` block at line 139. If the config file is not a valid YAML mapping, `load_config` raises `ValueError` which is not caught, producing a raw traceback instead of the friendly error message. Fix: move the entire `if output_dir is None:` block to be the FIRST statement inside the `try` block (i.e., indent it one level further so it sits between `try:` and the existing `cluster_config, values = ...` call). The `try` block already catches `ValueError`, so the error will then be handled correctly.

try:
cluster_config, values = kubernetes_aws_workflow.plan_from_config(
config,
Expand Down
5 changes: 5 additions & 0 deletions src/deepgram_self_hosted/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@
KUBERNETES_AWS_ARTIFACTS_DIR = KUBERNETES_AWS_DIR / "artifacts"

DOCKER_AWS_SCRIPT = DOCKER_AWS_DIR / "deepgram-aws-docker-setup.sh"


def artifacts_dir_for(name: str) -> Path:
"""Per-deployment subfolder under the EKS artifacts root."""
return KUBERNETES_AWS_ARTIFACTS_DIR / name
56 changes: 44 additions & 12 deletions src/deepgram_self_hosted/providers/kubernetes_aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@
strip_secrets,
write_config,
)
from deepgram_self_hosted.paths import KUBERNETES_AWS_ARTIFACTS_DIR
from deepgram_self_hosted.paths import artifacts_dir_for
from deepgram_self_hosted.providers import aws_cli
from deepgram_self_hosted.providers.kubernetes_aws_workflow import (
CLUSTER_CONFIG_FILENAME,
HELM_VALUES_FILENAME,
_role_name,
ensure_efs_from_config,
render_cluster_config,
render_values,
)
from deepgram_self_hosted.runner import command_exists, run
from deepgram_self_hosted.summary import render_summary
from deepgram_self_hosted.wizard import edit_field, run_eks_wizard
from deepgram_self_hosted.wizard import edit_field, run_eks_wizard, validate_name

SESSION_FILENAME = "session.yaml"
EXPANDED_CLUSTER_CONFIG_FILENAME = "eksctl-expanded-cluster-config.yaml"

SECRET_ENV_VARS: dict[str, str] = {
"registry_username": "DG_REGISTRY_USERNAME",
Expand Down Expand Up @@ -71,13 +76,13 @@ def setup(console: Console, *, config_path: Path | None = None) -> None:
config_to_save = strip_secrets(config) if secrets_in_memory else config

if on_disk_path is None:
default_save = KUBERNETES_AWS_ARTIFACTS_DIR / "session.yaml"
save_path_str = questionary.text(
"Save config to (used for re-runs and EFS write-back)",
default=str(default_save),
).unsafe_ask().strip()
on_disk_path = Path(save_path_str) if save_path_str else default_save
artifact_dir = _prompt_for_artifact_folder(
str(get_path(config, "cluster", "name", default="deepgram-self-hosted-cluster")),
console,
)
on_disk_path = artifact_dir / SESSION_FILENAME

on_disk_path.parent.mkdir(parents=True, exist_ok=True)
write_config(on_disk_path, config_to_save)
console.print(f"Wrote config to [bold]{on_disk_path}[/bold] (mode 0600)")
if secrets_in_memory:
Expand All @@ -93,6 +98,33 @@ def setup(console: Console, *, config_path: Path | None = None) -> None:
_run_native_setup(on_disk_path, console, secrets_override=secrets_in_memory)


def _prompt_for_artifact_folder(default_name: str, console: Console) -> Path:
"""Ask for an artifact-folder name, looping until the user accepts or picks a fresh
folder. Defaults to `<artifacts-root>/<cluster-name>`.

If the resolved folder already exists with files in it, ask for confirmation
before overwriting; on decline, re-prompt.
"""
while True:
name = questionary.text(
"Folder name for deployment artifacts "
"(cluster-config, helm-values, session)",
default=default_name,
validate=validate_name,
).unsafe_ask().strip()

folder = artifacts_dir_for(name)
if folder.exists() and any(folder.iterdir()):
overwrite = questionary.confirm(
f"Folder {folder} already exists. Overwrite its contents?",
default=False,
).unsafe_ask()
if not overwrite:
console.print("[yellow]Pick a different name.[/yellow]")
continue
return folder


def _summary_loop(config: dict[str, Any], console: Console) -> str:
"""Show summary; let the user edit fields. Returns 'deploy', 'save', or 'cancel'."""
while True:
Expand Down Expand Up @@ -136,15 +168,15 @@ def _run_native_setup(

_preflight(console)

artifact_dir = KUBERNETES_AWS_ARTIFACTS_DIR
artifact_dir = config_path.parent
artifact_dir.mkdir(parents=True, exist_ok=True)
cluster_config_path = artifact_dir / "cluster-config.yaml"
cluster_config_path = artifact_dir / CLUSTER_CONFIG_FILENAME
cluster_config_path.write_text(render_cluster_config(config))
console.print(f"Wrote cluster config to [bold]{cluster_config_path}[/bold]")

if get_path(config, "actions", "dry_run", default=False):
if get_path(config, "actions", "expanded_eksctl_dry_run", default=False):
expanded = artifact_dir / "eksctl-expanded-cluster-config.yaml"
expanded = artifact_dir / EXPANDED_CLUSTER_CONFIG_FILENAME
result = run(
["eksctl", "create", "cluster", "-f", str(cluster_config_path), "--dry-run"]
)
Expand Down Expand Up @@ -211,7 +243,7 @@ def _run_native_setup(
)

config = load_config(config_path)
values_path = artifact_dir / "my-values.yaml"
values_path = artifact_dir / HELM_VALUES_FILENAME
values_path.write_text(render_values(config, resolve_aws=True))
console.print(f"Wrote Helm values to [bold]{values_path}[/bold]")

Expand Down
7 changes: 5 additions & 2 deletions src/deepgram_self_hosted/providers/kubernetes_aws_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
from deepgram_self_hosted.config import get_path, load_config, write_config
from deepgram_self_hosted.providers import aws_cli

CLUSTER_CONFIG_FILENAME = "cluster-config.yaml"
HELM_VALUES_FILENAME = "helm-values.yaml"


def plan_from_config(
config_path: Path,
Expand All @@ -20,8 +23,8 @@ def plan_from_config(
config = load_config(config_path)
output_dir.mkdir(parents=True, exist_ok=True)

cluster_config_path = output_dir / "cluster-config.yaml"
values_path = output_dir / "my-values.yaml"
cluster_config_path = output_dir / CLUSTER_CONFIG_FILENAME
values_path = output_dir / HELM_VALUES_FILENAME

cluster_config_path.write_text(render_cluster_config(config))
values_path.write_text(render_values(config, resolve_aws=resolve_aws))
Expand Down
20 changes: 19 additions & 1 deletion src/deepgram_self_hosted/wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from __future__ import annotations

import re
from typing import Any

import questionary
Expand All @@ -15,6 +16,21 @@
OTHER = "Other (enter custom)"
DEFAULT_TAG = " (default)"

# Used for cluster name and artifact folder name. A subset of AWS EKS naming
# (alphanumeric + `-`) that is also filename-safe on Linux/macOS/Windows.
NAME_PATTERN = re.compile(r"^[A-Za-z0-9._-]+$")
NAME_HINT = "Use letters, digits, '.', '-', or '_' only."


def validate_name(value: str) -> bool | str:
"""Questionary validator for cluster/folder names."""
stripped = value.strip()
if not stripped:
Comment on lines +19 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAJOR SECURITY validate_name allows '..' enabling path traversal into artifacts_dir_for

wizard.py's NAME_PATTERN (^[A-Za-z0-9._-]+$) accepts .. and . as valid names. kubernetes_aws.py passes the validated name directly to artifacts_dir_for(name) (paths.py), so artifacts_dir_for('..') resolves to kubernetes/aws/ — one level above the intended artifacts root — silently writing cluster-config, helm-values, and session files there.

Suggested change
# Used for cluster name and artifact folder name. A subset of AWS EKS naming
# (alphanumeric + `-`) that is also filename-safe on Linux/macOS/Windows.
NAME_PATTERN = re.compile(r"^[A-Za-z0-9._-]+$")
NAME_HINT = "Use letters, digits, '.', '-', or '_' only."
def validate_name(value: str) -> bool | str:
"""Questionary validator for cluster/folder names."""
stripped = value.strip()
if not stripped:
NAME_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_-]*(?:\.[A-Za-z0-9_-]+)*$")
Prompt to fix with AI

Copy this prompt into your AI coding assistant to fix this issue.

In `src/deepgram_self_hosted/wizard.py`, tighten `NAME_PATTERN` so that `.` and `..` (and names starting/ending with a dot) are rejected. A minimal fix: require the name to start and end with an alphanumeric character, e.g. `re.compile(r'^[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9]$|^[A-Za-z0-9]$')`. Add a test in `test_validate_name_rejects_unsafe_characters` for `validate_name('..')` and `validate_name('.')`. The validator is consumed by `_prompt_for_artifact_folder` in `kubernetes_aws.py` which passes the result straight to `artifacts_dir_for(name)` with no further sanitization.

return "Required."
if not NAME_PATTERN.fullmatch(stripped):
return NAME_HINT
return True

REGIONS = [
"us-west-1", "us-west-2", "us-east-1", "us-east-2",
"eu-west-1", "eu-central-1",
Expand Down Expand Up @@ -109,7 +125,9 @@ def run_eks_wizard() -> dict[str, Any]:
config = default_eks_config()

config["cluster"]["name"] = questionary.text(
"Cluster name", default=config["cluster"]["name"]
"Cluster name",
default=config["cluster"]["name"],
validate=validate_name,
).unsafe_ask().strip()
config["cluster"]["region"] = _select_with_other(
"AWS region", REGIONS, config["cluster"]["region"]
Expand Down
Loading