Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f34039b
Add cluster, core provider, env-to-cluster, Vault role, Minos image a…
filippo-20tab Apr 28, 2026
8cda127
Add cluster, core providers and env-to-cluster prompts to collector a…
filippo-20tab Apr 28, 2026
e2c1169
Add cluster and Minos defaults to cookiecutter context
filippo-20tab Apr 28, 2026
25bc17b
Remove legacy terraform/, scripts/deploy/, traefik/, .gitlab-ci.yml a…
filippo-20tab Apr 28, 2026
504a4b9
Add platform .gitlab-ci.yml with minos/platform image
filippo-20tab Apr 28, 2026
000b4ab
Add vault-project.tfvars.example for Phase A admin setup
filippo-20tab Apr 28, 2026
6226ef2
Group TFC workspaces under tfe_project with platform/service naming
filippo-20tab Apr 28, 2026
4f28bab
Drop REGISTRY_PASSWORD and REGISTRY_USERNAME group variables
filippo-20tab Apr 28, 2026
7734484
Generate per-cluster minos tfvars skeletons after cookiecutter
filippo-20tab Apr 28, 2026
41b7410
Pass clusters, services and per-cluster providers to TFC module
filippo-20tab Apr 28, 2026
97ba67a
Move service workspace creation back to sub-bootstrappers
filippo-20tab Apr 28, 2026
6dd92a6
Switch Vault paths from stacks to platforms (per-cluster) layer
filippo-20tab Apr 28, 2026
a42dd9d
Pass env-to-cluster, Minos and toolchain versions to sub-bootstrappers
filippo-20tab Apr 28, 2026
c7bb92b
Drop Terraform.gitignore section now that .tf files live in Minos
filippo-20tab Apr 28, 2026
e6ae91d
Rewrite README for the cluster/env Minos platform model
filippo-20tab Apr 28, 2026
6da3c0a
Update tests for set_clusters and set_envs collector methods
filippo-20tab Apr 28, 2026
6d371c7
Drop legacy stacks, deployment-type, environments-distribution and ot…
filippo-20tab Apr 28, 2026
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
131 changes: 49 additions & 82 deletions bootstrap/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@
AWS_S3_REGION_DEFAULT,
BACKEND_TYPE_CHOICES,
BACKEND_TYPE_DEFAULT,
DEPLOYMENT_TYPE_CHOICES,
DEPLOYMENT_TYPE_DIGITALOCEAN,
DEPLOYMENT_TYPE_OTHER,
CLUSTERS_DEFAULT,
CORE_PROVIDER_CHOICES,
CORE_PROVIDER_DIGITALOCEAN,
DIGITALOCEAN_DATABASE_CLUSTER_NODE_SIZE_DEFAULT,
DIGITALOCEAN_REDIS_CLUSTER_NODE_SIZE_DEFAULT,
DIGITALOCEAN_SPACES_REGION_DEFAULT,
EMPTY_SERVICE_TYPE,
ENVIRONMENTS_DISTRIBUTION_CHOICES,
ENVIRONMENTS_DISTRIBUTION_DEFAULT,
ENVIRONMENTS_DISTRIBUTION_PROMPT,
ENV_NAMES,
ENV_TO_CLUSTER_DEFAULT,
FRONTEND_TYPE_CHOICES,
FRONTEND_TYPE_DEFAULT,
GITLAB_URL_DEFAULT,
Expand Down Expand Up @@ -57,7 +56,6 @@ class Collector:
frontend_type: str | None = None
frontend_service_slug: str | None = None
frontend_service_port: int | None = None
deployment_type: str | None = None
terraform_backend: str | None = None
terraform_cloud_hostname: str | None = None
terraform_cloud_token: str | None = None
Expand All @@ -67,10 +65,9 @@ class Collector:
vault_token: str | None = None
vault_url: str | None = None
digitalocean_token: str | None = None
kubernetes_cluster_ca_certificate: str | None = None
kubernetes_host: str | None = None
kubernetes_token: str | None = None
environments_distribution: str | None = None
clusters: list[str] | None = None
cluster_core_providers: dict[str, list[str]] | None = None
env_to_cluster: dict[str, str] | None = None
project_domain: str | None = None
subdomain_dev: str | None = None
subdomain_stage: str | None = None
Expand Down Expand Up @@ -124,7 +121,6 @@ def __post_init__(self):
"""Finalize initialization."""
self._service_dir = None
self._digitalocean_enabled = False
self._other_kubernetes_enabled = False

def collect(self):
"""Collect options."""
Expand All @@ -137,8 +133,8 @@ def collect(self):
self.set_use_redis()
self.set_terraform()
self.set_vault()
self.set_deployment_type()
self.set_environments_distribution()
self.set_clusters()
self.set_envs()
self.set_domain_and_urls()
self.set_letsencrypt()
self.set_deployment()
Expand Down Expand Up @@ -271,26 +267,38 @@ def set_vault(self):
)
self.vault_url = validate_or_prompt_url("Vault address", self.vault_url)

def set_deployment_type(self):
"""Set the deployment type option."""
if self.deployment_type not in DEPLOYMENT_TYPE_CHOICES:
self.deployment_type = click.prompt(
"Deploy type",
default=DEPLOYMENT_TYPE_DIGITALOCEAN,
type=click.Choice(DEPLOYMENT_TYPE_CHOICES, case_sensitive=False),
).lower()

def set_environments_distribution(self):
"""Set the environments distribution option."""
# TODO: forcing a single stack when deployment is `k8s-other` should be removed,
# and `set_deployment_type` merged with `set_deployment`
if self.deployment_type == DEPLOYMENT_TYPE_OTHER:
self.environments_distribution = "1"
elif self.environments_distribution not in ENVIRONMENTS_DISTRIBUTION_CHOICES:
self.environments_distribution = click.prompt(
ENVIRONMENTS_DISTRIBUTION_PROMPT,
default=ENVIRONMENTS_DISTRIBUTION_DEFAULT,
type=click.Choice(ENVIRONMENTS_DISTRIBUTION_CHOICES),
def set_clusters(self):
"""Set the clusters and per-cluster core providers."""
if not self.clusters:
raw = click.prompt(
"Comma-separated cluster slugs",
default=",".join(CLUSTERS_DEFAULT),
)
self.clusters = [slugify(c) for c in raw.split(",") if c.strip()]
self.cluster_core_providers = self.cluster_core_providers or {}
for cluster in self.clusters:
if cluster in self.cluster_core_providers:
continue
raw = click.prompt(
f"Comma-separated core providers for cluster '{cluster}'",
default=",".join(CORE_PROVIDER_CHOICES),
)
self.cluster_core_providers[cluster] = [
p.strip().lower()
for p in raw.split(",")
if p.strip().lower() in CORE_PROVIDER_CHOICES
]

def set_envs(self):
"""Set the environment-to-cluster mapping (one cluster slug per environment)."""
self.env_to_cluster = self.env_to_cluster or {}
for env_name in ENV_NAMES:
if env_name in self.env_to_cluster:
continue
self.env_to_cluster[env_name] = click.prompt(
f"Cluster slug hosting the '{env_name}' environment",
default=ENV_TO_CLUSTER_DEFAULT[env_name],
type=click.Choice(self.clusters, case_sensitive=False),
)

def set_domain_and_urls(self):
Expand Down Expand Up @@ -341,12 +349,11 @@ def set_letsencrypt(self):

def set_deployment(self):
"""Set the deployment options."""
if "digitalocean" in self.deployment_type:
if any(
CORE_PROVIDER_DIGITALOCEAN in providers
for providers in (self.cluster_core_providers or {}).values()
):
self.set_digitalocean()
elif self.deployment_type == DEPLOYMENT_TYPE_OTHER:
self.set_kubernetes()
else:
raise ValueError("Invalid deployment type.")

def set_digitalocean(self):
"""Set the DigitalOcean options."""
Expand Down Expand Up @@ -393,44 +400,6 @@ def set_digitalocean_token(self):
"DigitalOcean token", self.digitalocean_token
)

def set_kubernetes(self):
"""Set the Kubernetes options."""
self._other_kubernetes_enabled = True
# TODO: these settings should be different for each stack
self.kubernetes_cluster_ca_certificate = (
self.kubernetes_cluster_ca_certificate
or click.prompt(
"Kubernetes cluster CA certificate",
type=click.Path(dir_okay=False, exists=True, resolve_path=True),
)
)
self.kubernetes_host = self.kubernetes_host or validate_or_prompt_url(
"Kubernetes host", self.kubernetes_host
)
self.kubernetes_token = self.kubernetes_token or validate_or_prompt_secret(
"Kubernetes token", self.kubernetes_token
)
self.postgres_image = self.postgres_image or click.prompt(
"Postgres Docker image", default="postgres:14"
)
self.postgres_persistent_volume_capacity = (
self.postgres_persistent_volume_capacity
or click.prompt("Postgres K8s PersistentVolume capacity", default="10Gi")
)
self.postgres_persistent_volume_claim_capacity = (
self.postgres_persistent_volume_claim_capacity or ""
)
self.postgres_persistent_volume_host_path = (
self.postgres_persistent_volume_host_path
or click.prompt("Postgres K8s PersistentVolume host path")
)
if self.use_redis:
self.redis_image = self.redis_image or click.prompt(
"Redis Docker image", default="redis:6.2"
)
else:
self.redis_image = ""

def set_sentry(self):
"""Set the Sentry options."""
if any((self.backend_service_slug, self.frontend_service_slug)) and (
Expand Down Expand Up @@ -582,7 +551,6 @@ def get_runner(self):
frontend_type=self.frontend_type,
frontend_service_slug=self.frontend_service_slug,
frontend_service_port=self.frontend_service_port,
deployment_type=self.deployment_type,
terraform_backend=self.terraform_backend,
terraform_cloud_hostname=self.terraform_cloud_hostname,
terraform_cloud_token=self.terraform_cloud_token,
Expand All @@ -592,10 +560,9 @@ def get_runner(self):
vault_token=self.vault_token,
vault_url=self.vault_url,
digitalocean_token=self.digitalocean_token,
kubernetes_cluster_ca_certificate=self.kubernetes_cluster_ca_certificate,
kubernetes_host=self.kubernetes_host,
kubernetes_token=self.kubernetes_token,
environments_distribution=self.environments_distribution,
clusters=self.clusters,
cluster_core_providers=self.cluster_core_providers,
env_to_cluster=self.env_to_cluster,
project_domain=self.project_domain,
subdomain_dev=self.subdomain_dev,
subdomain_stage=self.subdomain_stage,
Expand Down
111 changes: 51 additions & 60 deletions bootstrap/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,6 @@
BASE_DIR = Path(__file__).parent.parent
DUMPS_DIR = BASE_DIR / ".dumps"

# Stacks

# BEWARE: stack names must be suitable for inclusion in Vault paths

DEV_STACK_NAME = "development"

DEV_STACK_SLUG = "dev"

STAGE_STACK_NAME = "staging"

STAGE_STACK_SLUG = "stage"

MAIN_STACK_NAME = "main"

MAIN_STACK_SLUG = "main"

STACKS_CHOICES = {
"1": [{"name": MAIN_STACK_NAME, "slug": MAIN_STACK_SLUG}],
"2": [
{"name": DEV_STACK_NAME, "slug": DEV_STACK_SLUG},
{"name": MAIN_STACK_NAME, "slug": MAIN_STACK_SLUG},
],
"3": [
{"name": DEV_STACK_NAME, "slug": DEV_STACK_SLUG},
{"name": STAGE_STACK_NAME, "slug": STAGE_STACK_SLUG},
{"name": MAIN_STACK_NAME, "slug": MAIN_STACK_SLUG},
],
}

# Environments

# BEWARE: environment names must be suitable for inclusion in Vault paths
Expand All @@ -42,24 +13,15 @@

DEV_ENV_SLUG = "dev"

DEV_ENV_STACK_CHOICES: dict[str, str] = {
"1": MAIN_STACK_SLUG,
}

STAGE_ENV_NAME = "staging"

STAGE_ENV_SLUG = "stage"

STAGE_ENV_STACK_CHOICES: dict[str, str] = {
"1": MAIN_STACK_SLUG,
"2": DEV_STACK_SLUG,
}

PROD_ENV_NAME = "production"

PROD_ENV_SLUG = "prod"

PROD_ENV_STACK_CHOICES: dict[str, str] = {}
ENV_NAMES = [DEV_ENV_NAME, STAGE_ENV_NAME, PROD_ENV_NAME]

# Env vars

Expand Down Expand Up @@ -96,26 +58,6 @@

FRONTEND_TYPE_CHOICES = [FRONTEND_TYPE_DEFAULT, FRONTEND_TYPE_LIGHT, EMPTY_SERVICE_TYPE]

# Deployment type

DEPLOYMENT_TYPE_DIGITALOCEAN = "digitalocean-k8s"

DEPLOYMENT_TYPE_OTHER = "other-k8s"

DEPLOYMENT_TYPE_CHOICES = [DEPLOYMENT_TYPE_DIGITALOCEAN, DEPLOYMENT_TYPE_OTHER]

# Environments distribution

ENVIRONMENTS_DISTRIBUTION_DEFAULT = "1"

ENVIRONMENTS_DISTRIBUTION_CHOICES = [ENVIRONMENTS_DISTRIBUTION_DEFAULT, "2", "3"]

ENVIRONMENTS_DISTRIBUTION_PROMPT = """Choose the environments distribution:
1 - All environments share the same stack (Default)
2 - Dev and Stage environments share the same stack, Prod has its own
3 - Each environment has its own stack
"""

# DigitalOcean services

DIGITALOCEAN_DATABASE_CLUSTER_NODE_SIZE_DEFAULT = "db-s-1vcpu-2gb"
Expand Down Expand Up @@ -153,14 +95,63 @@

GITLAB_URL_DEFAULT = "https://gitlab.com"

# Clusters

CLUSTER_DEV_SLUG = "dev"

CLUSTER_MAIN_SLUG = "main"

CLUSTERS_DEFAULT = [CLUSTER_DEV_SLUG, CLUSTER_MAIN_SLUG]

# Core providers (per cluster, multi-select)

CORE_PROVIDER_AWS = "aws"

CORE_PROVIDER_DIGITALOCEAN = "digitalocean"

CORE_PROVIDER_CHOICES = [CORE_PROVIDER_AWS, CORE_PROVIDER_DIGITALOCEAN]

# Environment-to-cluster mapping

ENV_TO_CLUSTER_DEFAULT: dict[str, str] = {
DEV_ENV_NAME: CLUSTER_DEV_SLUG,
STAGE_ENV_NAME: CLUSTER_DEV_SLUG,
PROD_ENV_NAME: CLUSTER_MAIN_SLUG,
}

# Vault — auth roles are shared 20tab-wide on the gitlab-jwt backend

VAULT_PLATFORM_ROLE = "platform-gitlab-job"

VAULT_SERVICE_ROLE = "service-gitlab-job"

# Minos

MINOS_PLATFORM_IMAGE = "registry.gitlab.com/20tab-open/minos/platform:latest"

MINOS_SERVICE_IMAGE = "registry.gitlab.com/20tab-open/minos/service:latest"

# OpenTofu

OPENTOFU_COMPONENT_VERSION = "3.11.0"

OPENTOFU_VERSION = "1.10.6"

# Python

PYTHON_VERSION_DEFAULT = "3.14"

# Node

NODE_VERSION_DEFAULT = "24.14.0"

# Dump

DUMP_EXCLUDED_OPTIONS = (
"backend_sentry_dsn",
"digitalocean_token",
"frontend_sentry_dsn",
"gitlab_token",
"kubernetes_token",
"pact_broker_password",
"s3_access_id",
"s3_secret_key",
Expand Down
Loading
Loading