diff --git a/bootstrap/collector.py b/bootstrap/collector.py
index 7e66eb0a..ae135ded 100755
--- a/bootstrap/collector.py
+++ b/bootstrap/collector.py
@@ -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,
@@ -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
@@ -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
@@ -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."""
@@ -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()
@@ -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):
@@ -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."""
@@ -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 (
@@ -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,
@@ -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,
diff --git a/bootstrap/constants.py b/bootstrap/constants.py
index 6916de9e..ecbb137e 100755
--- a/bootstrap/constants.py
+++ b/bootstrap/constants.py
@@ -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
@@ -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
@@ -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"
@@ -153,6 +95,56 @@
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 = (
@@ -160,7 +152,6 @@
"digitalocean_token",
"frontend_sentry_dsn",
"gitlab_token",
- "kubernetes_token",
"pact_broker_password",
"s3_access_id",
"s3_secret_key",
diff --git a/bootstrap/runner.py b/bootstrap/runner.py
index 0831852e..144cbecc 100644
--- a/bootstrap/runner.py
+++ b/bootstrap/runner.py
@@ -9,8 +9,6 @@
import subprocess
from dataclasses import dataclass, field
from functools import partial
-from itertools import groupby
-from operator import itemgetter
from pathlib import Path
from time import time
@@ -20,31 +18,29 @@
from bootstrap.constants import (
BACKEND_TEMPLATE_URLS,
- DEPLOYMENT_TYPE_OTHER,
DEV_ENV_NAME,
DEV_ENV_SLUG,
- DEV_ENV_STACK_CHOICES,
- DEV_STACK_SLUG,
DUMPS_DIR,
+ ENV_TO_CLUSTER_DEFAULT,
FRONTEND_TEMPLATE_URLS,
GITLAB_URL_DEFAULT,
- MAIN_STACK_NAME,
- MAIN_STACK_SLUG,
MEDIA_STORAGE_DIGITALOCEAN_S3,
+ MINOS_PLATFORM_IMAGE,
+ MINOS_SERVICE_IMAGE,
+ NODE_VERSION_DEFAULT,
+ OPENTOFU_COMPONENT_VERSION,
+ OPENTOFU_VERSION,
PROD_ENV_NAME,
PROD_ENV_SLUG,
- PROD_ENV_STACK_CHOICES,
+ PYTHON_VERSION_DEFAULT,
SERVICE_SLUG_DEFAULT,
- STACKS_CHOICES,
STAGE_ENV_NAME,
STAGE_ENV_SLUG,
- STAGE_ENV_STACK_CHOICES,
- STAGE_STACK_SLUG,
SUBREPOS_DIR,
TERRAFORM_BACKEND_TFC,
)
from bootstrap.exceptions import BootstrapError
-from bootstrap.helpers import format_gitlab_variable, format_tfvar
+from bootstrap.helpers import format_gitlab_variable
error = partial(click.style, fg="red")
@@ -71,7 +67,6 @@ class Runner:
frontend_type: str
frontend_service_slug: str | None = None
frontend_service_port: int | None = None
- deployment_type: str
terraform_backend: str
terraform_cloud_hostname: str | None = None
terraform_cloud_token: str | None = None
@@ -81,10 +76,15 @@ class Runner:
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
+ clusters: list[str] | None = None
+ cluster_core_providers: dict[str, list[str]] | None = None
+ env_to_cluster: dict[str, str] | None = None
+ python_version: str = PYTHON_VERSION_DEFAULT
+ node_version: str = NODE_VERSION_DEFAULT
+ minos_platform_image: str = MINOS_PLATFORM_IMAGE
+ minos_service_image: str = MINOS_SERVICE_IMAGE
+ opentofu_component_version: str = OPENTOFU_COMPONENT_VERSION
+ opentofu_version: str = OPENTOFU_VERSION
project_domain: str | None = None
subdomain_dev: str | None = None
subdomain_stage: str | None = None
@@ -134,10 +134,8 @@ class Runner:
logs_dir: Path | None = None
run_id: str = field(init=False)
service_slug: str = field(init=False)
- stacks: list = field(init=False, default_factory=list)
envs: list = field(init=False, default_factory=list)
gitlab_variables: dict = field(init=False, default_factory=dict)
- tfvars: dict = field(init=False, default_factory=dict)
vault_secrets: dict = field(init=False, default_factory=dict)
terraform_run_modules: list = field(init=False, default_factory=list)
terraform_outputs: dict = field(init=False, default_factory=dict)
@@ -150,21 +148,21 @@ def __post_init__(self):
self.terraform_dir = self.terraform_dir or Path(f".terraform/{self.run_id}")
self.logs_dir = self.logs_dir or Path(f".logs/{self.run_id}")
- def set_stacks(self):
- """Set the stacks."""
- self.stacks = STACKS_CHOICES[self.environments_distribution]
-
def set_envs(self):
"""Set the envs."""
+ env_to_cluster = self.env_to_cluster or ENV_TO_CLUSTER_DEFAULT
+
+ def _host(url: str) -> str:
+ return (url or "").removeprefix("https://").removeprefix("http://").rstrip("/")
+
self.envs = [
{
"basic_auth_enabled": True,
"name": DEV_ENV_NAME,
"prefix": self.subdomain_dev,
"slug": DEV_ENV_SLUG,
- "stack_slug": DEV_ENV_STACK_CHOICES.get(
- self.environments_distribution, DEV_STACK_SLUG
- ),
+ "cluster_slug": env_to_cluster.get(DEV_ENV_NAME),
+ "host": _host(self.project_url_dev),
"url": self.project_url_dev,
},
{
@@ -172,9 +170,8 @@ def set_envs(self):
"name": STAGE_ENV_NAME,
"prefix": self.subdomain_stage,
"slug": STAGE_ENV_SLUG,
- "stack_slug": STAGE_ENV_STACK_CHOICES.get(
- self.environments_distribution, STAGE_STACK_SLUG
- ),
+ "cluster_slug": env_to_cluster.get(STAGE_ENV_NAME),
+ "host": _host(self.project_url_stage),
"url": self.project_url_stage,
},
{
@@ -182,9 +179,8 @@ def set_envs(self):
"name": PROD_ENV_NAME,
"prefix": self.subdomain_prod,
"slug": PROD_ENV_SLUG,
- "stack_slug": PROD_ENV_STACK_CHOICES.get(
- self.environments_distribution, MAIN_STACK_SLUG
- ),
+ "cluster_slug": env_to_cluster.get(PROD_ENV_NAME),
+ "host": _host(self.project_url_prod),
"url": self.project_url_prod,
},
]
@@ -259,18 +255,6 @@ def collect_gitlab_variables_secrets(self):
self.digitalocean_token and self.register_gitlab_group_variables(
("DIGITALOCEAN_TOKEN", self.digitalocean_token, True)
)
- if self.deployment_type == DEPLOYMENT_TYPE_OTHER:
- self.register_gitlab_group_variables(
- (
- "KUBERNETES_CLUSTER_CA_CERTIFICATE",
- base64.b64encode(
- Path(self.kubernetes_cluster_ca_certificate).read_bytes()
- ).decode(),
- True,
- ),
- ("KUBERNETES_HOST", self.kubernetes_host),
- ("KUBERNETES_TOKEN", self.kubernetes_token, True),
- )
if "s3" in self.media_storage:
self.register_gitlab_group_variables(
("S3_ACCESS_ID", self.s3_access_id, True),
@@ -291,128 +275,18 @@ def render_gitlab_variables_to_string(self, level):
f"{k} = {v}" for k, v in self.gitlab_variables.get(level, {}).items()
)
- def register_tfvar(self, tf_stage, var_name, var_value=None, var_type=None):
- """Register a Terraform variable value for the given stage."""
- vars_list = self.tfvars.setdefault(tf_stage, [])
- if var_value is None:
- var_value = getattr(self, var_name)
- vars_list.append("=".join((var_name, format_tfvar(var_value, var_type))))
-
- def register_tfvars(self, tf_stage, *vars):
- """Register one or more Terraform variable for the given stage."""
- [
- self.register_tfvar(tf_stage, *((i,) if isinstance(i, str) else i))
- for i in vars
- ]
-
- def register_base_tfvars(self, *vars, stack_slug=None):
- """Register one or more base Terraform variable."""
- tf_stage = "base" + (stack_slug and f"_{stack_slug}" or "")
- self.register_tfvars(tf_stage, *vars)
-
- def register_cluster_tfvars(self, *vars, stack_slug=None):
- """Register one or more cluster Terraform variable."""
- tf_stage = "cluster" + (stack_slug and f"_{stack_slug}" or "")
- self.register_tfvars(tf_stage, *vars)
-
- def register_environment_tfvars(self, *vars, env_slug=None):
- """Register one or more environment Terraform variable."""
- tf_stage = "environment" + (env_slug and f"_{env_slug}" or "")
- self.register_tfvars(tf_stage, *vars)
-
- def collect_tfvars(self):
- """Collect the base, cluster and environment Terraform variables."""
- self.register_environment_tfvars(("registry_server", "registry.gitlab.com"))
- backend_service_paths = ["/", f"/{self.backend_service_slug}"]
- frontend_service_paths = ["/", f"/{self.frontend_service_slug}"]
- if self.frontend_service_slug:
- self.register_environment_tfvars(
- ("frontend_service_paths", frontend_service_paths, "list"),
- ("frontend_service_port", None, "num"),
- "frontend_service_slug",
- )
- backend_service_paths = [
- "/admin",
- "/api",
- "/static",
- f"/{self.backend_service_slug}",
- ] + (["/media"] if self.media_storage == "local" else [])
- if self.backend_service_slug:
- self.register_environment_tfvars(
- ("backend_service_paths", backend_service_paths, "list"),
- ("backend_service_port", None, "num"),
- "backend_service_slug",
- )
- self.project_domain and self.register_environment_tfvars("project_domain")
- if self.letsencrypt_certificate_email:
- self.register_cluster_tfvars("letsencrypt_certificate_email")
- self.register_environment_tfvars("letsencrypt_certificate_email")
- self.subdomain_monitoring and self.register_environment_tfvars(
- ("monitoring_subdomain", self.subdomain_monitoring), env_slug="prod"
- )
- if self.use_redis:
- self.register_base_tfvars(("use_redis", True, "bool"))
- self.register_environment_tfvars(("use_redis", True, "bool"))
- if "digitalocean" in self.deployment_type:
- self.register_environment_tfvars(
- ("create_dns_records", self.digitalocean_dns_records_create, "bool"),
- )
- self.digitalocean_domain_create and self.register_environment_tfvars(
- ("create_domain", True, "bool"), env_slug=DEV_ENV_SLUG
- )
- self.register_base_tfvars(
- ("k8s_cluster_region", self.digitalocean_k8s_cluster_region),
- ("database_cluster_region", self.digitalocean_database_cluster_region),
- (
- "database_cluster_node_size",
- self.digitalocean_database_cluster_node_size,
- ),
- )
- self.use_redis and self.register_base_tfvars(
- ("redis_cluster_region", self.digitalocean_redis_cluster_region),
- ("redis_cluster_node_size", self.digitalocean_redis_cluster_node_size),
- )
- elif self.deployment_type == DEPLOYMENT_TYPE_OTHER:
- self.register_environment_tfvars(
- "postgres_image",
- "postgres_persistent_volume_capacity",
- "postgres_persistent_volume_claim_capacity",
- "postgres_persistent_volume_host_path",
- )
- self.use_redis and self.register_environment_tfvars("redis_image")
- if self.media_storage == MEDIA_STORAGE_DIGITALOCEAN_S3:
- self.register_base_tfvars(("create_s3_bucket", True, "bool"))
- self.register_environment_tfvars(
- ("digitalocean_spaces_bucket_available", True, "bool")
- )
- for env in self.envs:
- env_slug = env["slug"]
- self.register_environment_tfvars(
- ("basic_auth_enabled", env["basic_auth_enabled"], "bool"),
- ("stack_slug", env["stack_slug"]),
- ("subdomains", [getattr(self, f"subdomain_{env_slug}")], "list"),
- env_slug=env_slug,
- )
-
- def register_vault_stack_secret(
- self, stack_name, stack_envs_names, secret_name, secret_data
- ):
- """Register a Vault stack secret locally, optionally copying it to the envs."""
- self.vault_secrets[f"stacks/{stack_name}/{secret_name}"] = secret_data
- [
- self.register_vault_environment_secret(i, secret_name, secret_data)
- for i in stack_envs_names
- ]
+ def register_vault_platform_secret(self, cluster_slug, secret_name, secret_data):
+ """Register a Vault platform secret at platforms/{cluster}/{name}."""
+ self.vault_secrets[f"platforms/{cluster_slug}/{secret_name}"] = secret_data
def register_vault_environment_secret(self, env_name, secret_name, secret_data):
"""Register a Vault environment secret locally."""
self.vault_secrets[f"envs/{env_name}/{secret_name}"] = secret_data
- def collect_vault_stack_secrets(self, stack_name, stack_envs_names):
- """Collect the Vault secrets for the given stack."""
- self.digitalocean_token and self.register_vault_stack_secret(
- stack_name,
- stack_envs_names,
+ def collect_vault_platform_secrets(self, cluster_slug):
+ """Collect the Vault secrets for the given cluster (platform layer)."""
+ self.digitalocean_token and self.register_vault_platform_secret(
+ cluster_slug,
"digitalocean",
{"digitalocean_token": self.digitalocean_token},
)
@@ -426,33 +300,7 @@ def collect_vault_stack_secrets(self, stack_name, stack_envs_names):
self.media_storage == MEDIA_STORAGE_DIGITALOCEAN_S3 and s3_secret.update(
s3_host=self.s3_host
)
- self.register_vault_stack_secret(
- stack_name, stack_envs_names, "s3", s3_secret
- )
- (
- self.subdomain_monitoring
- and stack_name == MAIN_STACK_NAME
- and self.register_vault_stack_secret(
- stack_name,
- stack_envs_names,
- "monitoring",
- {"grafana_password": secrets.token_urlsafe(12)},
- )
- )
- (
- self.deployment_type == DEPLOYMENT_TYPE_OTHER
- and self.register_vault_stack_secret(
- stack_name,
- "k8s",
- {
- "kubernetes_cluster_ca_certificate": base64.b64encode(
- Path(self.kubernetes_cluster_ca_certificate).read_bytes()
- ).decode(),
- "kubernetes_host": self.kubernetes_host,
- "kubernetes_token": self.kubernetes_token,
- },
- )
- )
+ self.register_vault_platform_secret(cluster_slug, "s3", s3_secret)
def collect_vault_environment_secrets(self, env_name):
"""Collect the Vault secrets for the given environment."""
@@ -495,18 +343,14 @@ def collect_vault_secrets(self):
"registry_username": gitlab_terraform_outputs["registry_username"],
"registry_password": gitlab_terraform_outputs["registry_password"],
}
- stacks_mapping = {i["slug"]: i["name"] for i in self.stacks}
- for stack_slug, stack_envs in groupby(self.envs, key=itemgetter("stack_slug")):
- stack_name = stacks_mapping[stack_slug]
- stack_envs_names = []
- for env in stack_envs:
- env_name = env["name"]
- self.collect_vault_environment_secrets(env_name)
- regcred and self.register_vault_environment_secret(
- env_name, f"{self.service_slug}/regcred", regcred
- )
- stack_envs_names.append(env_name)
- self.collect_vault_stack_secrets(stack_name, stack_envs_names)
+ for cluster_slug in self.clusters or []:
+ self.collect_vault_platform_secrets(cluster_slug)
+ for env in self.envs:
+ env_name = env["name"]
+ self.collect_vault_environment_secrets(env_name)
+ regcred and self.register_vault_environment_secret(
+ env_name, f"{self.service_slug}/regcred", regcred
+ )
self.pact_broker_url and self.collect_vault_pact_secrets()
def init_service(self):
@@ -518,26 +362,74 @@ def init_service(self):
"backend_service_port": self.backend_service_port,
"backend_service_slug": self.backend_service_slug,
"backend_type": self.backend_type,
- "deployment_type": self.deployment_type,
- "environments_distribution": self.environments_distribution,
"frontend_service_port": self.frontend_service_port,
"frontend_service_slug": self.frontend_service_slug,
"frontend_type": self.frontend_type,
"media_storage": self.media_storage,
+ "minos_platform_image": self.minos_platform_image,
+ "minos_service_image": self.minos_service_image,
+ "opentofu_component_version": self.opentofu_component_version,
+ "opentofu_version": self.opentofu_version,
"project_dirname": self.project_dirname,
"project_name": self.project_name,
"project_slug": self.project_slug,
- "resources": {"envs": self.envs, "stacks": self.stacks},
+ "python_version": self.python_version,
+ "resources": {"envs": self.envs},
"service_slug": self.service_slug,
"terraform_backend": self.terraform_backend,
"terraform_cloud_organization": self.terraform_cloud_organization,
- "tfvars": self.tfvars,
"use_pact": self.pact_broker_url and "true" or "false",
"use_vault": self.vault_url and "true" or "false",
},
output_dir=self.output_dir,
no_input=True,
)
+ self.render_minos_per_cluster_files()
+
+ def render_minos_per_cluster_files(self):
+ """Write per-cluster minos tfvars skeletons (core/{provider}.tfvars + kubernetes.tfvars)."""
+ click.echo(info("...generating per-cluster minos files"))
+ clusters = self.clusters or []
+ cluster_core_providers = self.cluster_core_providers or {}
+ letsencrypt_email = self.letsencrypt_certificate_email or "tech@20tab.com"
+ platform_dir = self.output_dir / self.project_dirname / "minos"
+ for cluster in clusters:
+ cluster_full = f"{self.project_slug}-{cluster}"
+ cluster_dir = platform_dir / cluster
+ (cluster_dir / "core").mkdir(parents=True, exist_ok=True)
+ namespaces = sorted(
+ {f"{self.project_slug}-{env['slug']}" for env in self.envs if env.get("cluster_slug") == cluster}
+ )
+ traefik_host = (
+ f"proxy-{cluster}.{self.project_domain}" if self.project_domain else ""
+ )
+ for provider in cluster_core_providers.get(cluster, []):
+ if provider == "digitalocean":
+ (cluster_dir / "core" / "digitalocean.tfvars").write_text(
+ f'cluster_slug = "{cluster_full}"\n'
+ 'create_database = true\n'
+ 'create_valkey = false\n'
+ 'database_cluster_node_size = "db-s-1vcpu-2gb"\n'
+ 'database_cluster_storage_size = 10\n'
+ 'k8s_cluster_node_count = 1\n'
+ 'k8s_cluster_node_size = "s-2vcpu-4gb"\n'
+ f'project_name = "{self.project_name}"\n'
+ )
+ elif provider == "aws":
+ (cluster_dir / "core" / "aws.tfvars").write_text(
+ f'cluster_slug = "{cluster_full}"\n'
+ 'iam_permissions_boundary_name = ""\n'
+ 'iam_user_name_prefix = ""\n'
+ 'iam_users = {}\n'
+ 'kms_keys = {}\n'
+ )
+ (cluster_dir / "kubernetes.tfvars").write_text(
+ f'cluster_slug = "{cluster_full}"\n'
+ 'managed_secrets = {}\n'
+ f'namespaces = {json.dumps(namespaces)}\n'
+ f'traefik_dashboard_host = "{traefik_host}"\n'
+ f'traefik_dashboard_letsencrypt_email = "{letsencrypt_email}"\n'
+ )
def create_env_file(self):
"""Create the final env file from its template."""
@@ -585,16 +477,15 @@ def init_terraform_cloud(self):
click.echo(info("...creating the Terraform Cloud resources with Terraform"))
env = {
"TF_VAR_admin_email": self.terraform_cloud_admin_email,
+ "TF_VAR_cluster_core_providers": json.dumps(self.cluster_core_providers or {}),
+ "TF_VAR_clusters": json.dumps(self.clusters or []),
"TF_VAR_create_organization": self.terraform_cloud_organization_create
and "true"
or "false",
- "TF_VAR_environments": json.dumps(list(map(itemgetter("slug"), self.envs))),
"TF_VAR_hostname": self.terraform_cloud_hostname,
"TF_VAR_organization_name": self.terraform_cloud_organization,
"TF_VAR_project_name": self.project_name,
"TF_VAR_project_slug": self.project_slug,
- "TF_VAR_service_slug": self.service_slug,
- "TF_VAR_stacks": json.dumps(list(map(itemgetter("slug"), self.stacks))),
"TF_VAR_terraform_cloud_token": self.terraform_cloud_token,
}
self.run_terraform("terraform-cloud", env)
@@ -770,8 +661,7 @@ def init_subrepo(self, service_slug, template_url, **kwargs):
]
)
options = {
- "deployment_type": self.deployment_type,
- "environments_distribution": self.environments_distribution,
+ "env_to_cluster": self.env_to_cluster,
"gid": self.gid,
"gitlab_namespace_path": str(
Path(self.gitlab_namespace_path) / self.gitlab_group_slug
@@ -779,6 +669,9 @@ def init_subrepo(self, service_slug, template_url, **kwargs):
"gitlab_token": self.gitlab_token,
"gitlab_url": self.gitlab_url,
"logs_dir": str(self.logs_dir.resolve()),
+ "minos_service_image": self.minos_service_image,
+ "opentofu_component_version": self.opentofu_component_version,
+ "opentofu_version": self.opentofu_version,
"output_dir": str(self.service_dir.resolve()),
"project_dirname": service_slug,
"project_name": self.project_name,
@@ -838,9 +731,7 @@ def cleanup(self):
def run(self):
"""Run the bootstrap."""
click.echo(highlight(f"Initializing the {self.service_slug} service:"))
- self.set_stacks()
self.set_envs()
- self.collect_tfvars()
self.collect_gitlab_variables()
self.init_service()
self.create_env_file()
@@ -859,6 +750,7 @@ def run(self):
and (f"http://{self.backend_service_slug}:{self.backend_service_port}")
or None,
internal_service_port=self.frontend_service_port,
+ node_version=self.node_version,
sentry_dsn=self.frontend_sentry_dsn,
)
backend_template_url = BACKEND_TEMPLATE_URLS.get(self.backend_type)
@@ -868,6 +760,7 @@ def run(self):
backend_template_url,
internal_service_port=self.backend_service_port,
media_storage=self.media_storage,
+ python_version=self.python_version,
sentry_dsn=self.backend_sentry_dsn,
)
self.change_output_owner()
diff --git a/cookiecutter.json b/cookiecutter.json
index f92650ad..4411eef0 100644
--- a/cookiecutter.json
+++ b/cookiecutter.json
@@ -14,35 +14,32 @@
"media_storage": ["digitalocean-s3", "aws-s3", "local", "none"],
"use_pact": "false",
"use_vault": "false",
+ "python_version": "3.14",
+ "minos_platform_image": "registry.gitlab.com/20tab-open/minos/platform:latest",
+ "minos_service_image": "registry.gitlab.com/20tab-open/minos/service:latest",
+ "opentofu_component_version": "3.11.0",
+ "opentofu_version": "1.10.6",
"resources": {
- "stacks": [
- [
- {
- "name": "main",
- "slug": "main"
- }
- ]
- ],
"envs": [
{
"name": "development",
"slug": "dev",
- "stack_slug": "main"
+ "cluster_slug": "dev",
+ "host": ""
},
{
"name": "staging",
"slug": "stage",
- "stack_slug": "main"
+ "cluster_slug": "dev",
+ "host": ""
},
{
"name": "production",
"slug": "prod",
- "stack_slug": "main"
+ "cluster_slug": "main",
+ "host": ""
}
]
},
- "deployment_type": ["digitalocean-k8s", "other-k8s"],
- "environments_distribution": "1",
- "tfvars": {},
"_extensions": ["cookiecutter.extensions.SlugifyExtension"]
}
diff --git a/terraform/gitlab/main.tf b/terraform/gitlab/main.tf
index a051f816..da2ba5a6 100644
--- a/terraform/gitlab/main.tf
+++ b/terraform/gitlab/main.tf
@@ -197,26 +197,6 @@ resource "gitlab_group_variable" "vars" {
masked = lookup(each.value, "masked", false)
}
-resource "gitlab_group_variable" "registry_password" {
- count = var.use_vault ? 0 : 1
-
- group = local.group_id
- key = "REGISTRY_PASSWORD"
- value = gitlab_deploy_token.regcred.token
- protected = true
- masked = true
-}
-
-resource "gitlab_group_variable" "registry_username" {
- count = var.use_vault ? 0 : 1
-
- group = local.group_id
- key = "REGISTRY_USERNAME"
- value = gitlab_deploy_token.regcred.username
- protected = true
- masked = true
-}
-
/* Project Variables */
resource "gitlab_project_variable" "vars" {
diff --git a/terraform/terraform-cloud/main.tf b/terraform/terraform-cloud/main.tf
index 8ec9c967..a27dddf8 100644
--- a/terraform/terraform-cloud/main.tf
+++ b/terraform/terraform-cloud/main.tf
@@ -1,38 +1,38 @@
locals {
organization = var.create_organization ? tfe_organization.main[0] : data.tfe_organization.main[0]
- workspaces = concat(
- flatten(
- [
- for stage in ["base", "cluster"] :
- [
- for stack in var.stacks :
- {
- name = "${var.project_slug}_${var.service_slug}_${stage}_${stack}"
- description = "${var.project_name} project, ${var.service_slug} service, ${stack} stack, ${stage} stage"
- tags = [
- "project:${var.project_slug}",
- "service:${var.service_slug}",
- "stack:${stack}",
- "stage:${stage}",
- ]
- }
- ]
- ]
- ),
- [
- for env in var.environments :
- {
- name = "${var.project_slug}_${var.service_slug}_environment_${env}"
- description = "${var.project_name} project, ${var.service_slug} service, ${env} environment"
+ platform_core_workspaces = flatten([
+ for cluster in var.clusters : [
+ for provider in lookup(var.cluster_core_providers, cluster, []) : {
+ name = "${var.project_slug}_platform_${cluster}_core_${provider}"
+ description = "${var.project_name} platform, ${cluster} cluster, ${provider} core."
tags = [
- "env:${env}",
"project:${var.project_slug}",
- "service:${var.service_slug}",
- "stage:environment",
+ "layer:platform",
+ "cluster:${cluster}",
+ "component:core",
+ "provider:${provider}",
]
}
]
+ ])
+
+ platform_kubernetes_workspaces = [
+ for cluster in var.clusters : {
+ name = "${var.project_slug}_platform_${cluster}_kubernetes"
+ description = "${var.project_name} platform, ${cluster} cluster, kubernetes layer."
+ tags = [
+ "project:${var.project_slug}",
+ "layer:platform",
+ "cluster:${cluster}",
+ "component:kubernetes",
+ ]
+ }
+ ]
+
+ workspaces = concat(
+ local.platform_core_workspaces,
+ local.platform_kubernetes_workspaces,
)
}
@@ -43,7 +43,7 @@ terraform {
required_providers {
tfe = {
source = "hashicorp/tfe"
- version = "~> 0.53"
+ version = "~> 0.70"
}
}
}
@@ -68,6 +68,15 @@ resource "tfe_organization" "main" {
email = var.admin_email
}
+/* Project (groups all workspaces and inherits the local execution mode) */
+
+resource "tfe_project" "main" {
+ organization = local.organization.name
+ name = var.project_slug
+ description = "${var.project_name} project workspaces."
+ default_execution_mode = "local"
+}
+
/* Workspaces */
resource "tfe_workspace" "main" {
@@ -76,12 +85,6 @@ resource "tfe_workspace" "main" {
name = each.value.name
description = each.value.description
organization = local.organization.name
+ project_id = tfe_project.main.id
tag_names = each.value.tags
}
-
-resource "tfe_workspace_settings" "main" {
- for_each = tfe_workspace.main
-
- workspace_id = each.value.id
- execution_mode = "local"
-}
diff --git a/terraform/terraform-cloud/variables.tf b/terraform/terraform-cloud/variables.tf
index 193c7e4a..8c8a6f0f 100644
--- a/terraform/terraform-cloud/variables.tf
+++ b/terraform/terraform-cloud/variables.tf
@@ -10,12 +10,6 @@ variable "create_organization" {
default = false
}
-variable "environments" {
- description = "The list of environments slugs."
- type = list(string)
- default = []
-}
-
variable "hostname" {
description = "The Terraform Cloud hostname."
type = string
@@ -37,17 +31,18 @@ variable "project_slug" {
type = string
}
-variable "service_slug" {
- description = "The service slug."
- type = string
-}
-
-variable "stacks" {
- description = "The list of stacks slugs."
+variable "clusters" {
+ description = "The list of cluster slugs (e.g. [\"dev\", \"main\"])."
type = list(string)
default = []
}
+variable "cluster_core_providers" {
+ description = "Per-cluster core providers map (e.g. {dev = [\"aws\", \"digitalocean\"]})."
+ type = map(list(string))
+ default = {}
+}
+
variable "terraform_cloud_token" {
description = "The Terraform Cloud token."
type = string
diff --git a/tests/test_collector.py b/tests/test_collector.py
index f4294fdd..01d5556d 100755
--- a/tests/test_collector.py
+++ b/tests/test_collector.py
@@ -345,65 +345,6 @@ def test_vault_from_input_and_options(self):
self.assertEqual(collector.vault_url, "https://vault.test.com")
self.assertEqual(len(mocked_confirm.mock_calls), 1)
- def test_deployment_type_from_default(self):
- """Test collecting the deployment type from its default value."""
- collector = Collector()
- self.assertIsNone(collector.deployment_type)
- with mock_input(""):
- collector.set_deployment_type()
- self.assertEqual(collector.deployment_type, "digitalocean-k8s")
-
- def test_deployment_type_from_input(self):
- """Test collecting the deployment type from user input."""
- collector = Collector()
- self.assertIsNone(collector.deployment_type)
- with mock_input("bad-deployment-type", "yet-another-bad-value", "other-k8s"):
- collector.set_deployment_type()
- self.assertEqual(collector.deployment_type, "other-k8s")
-
- def test_deployment_type_from_options(self):
- """Test collecting the deployment type from the collected options."""
- collector = Collector(deployment_type="other-k8s")
- self.assertEqual(collector.deployment_type, "other-k8s")
- with mock.patch("bootstrap.collector.click.prompt") as mocked_prompt:
- collector.set_deployment_type()
- self.assertEqual(collector.deployment_type, "other-k8s")
- mocked_prompt.assert_not_called()
-
- def test_environments_distribution_for_other_k8s_deployment(self):
- """Test collecting the environments distribution for other-k8s deployment."""
- collector = Collector(deployment_type="other-k8s")
- self.assertIsNone(collector.environments_distribution)
- with mock.patch("bootstrap.collector.click.prompt") as mocked_prompt:
- collector.set_environments_distribution()
- self.assertEqual(collector.environments_distribution, "1")
- mocked_prompt.assert_not_called()
-
- def test_environments_distribution_from_default(self):
- """Test collecting the environments distribution from its default value."""
- collector = Collector()
- self.assertIsNone(collector.environments_distribution)
- with mock_input(""):
- collector.set_environments_distribution()
- self.assertEqual(collector.environments_distribution, "1")
-
- def test_environments_distribution_from_input(self):
- """Test collecting the environments distribution from user input."""
- collector = Collector()
- self.assertIsNone(collector.environments_distribution)
- with mock_input("one", "yet-another-bad-value", "3"):
- collector.set_environments_distribution()
- self.assertEqual(collector.environments_distribution, "3")
-
- def test_environments_distribution_from_options(self):
- """Test collecting the environments distribution from the collected options."""
- collector = Collector(environments_distribution="2")
- self.assertEqual(collector.environments_distribution, "2")
- with mock.patch("bootstrap.collector.click.prompt") as mocked_prompt:
- collector.set_environments_distribution()
- self.assertEqual(collector.environments_distribution, "2")
- mocked_prompt.assert_not_called()
-
def test_set_domain_and_urls_from_default(self):
"""Test collecting the domain and urls options from default."""
collector = Collector(project_slug="test-project")
@@ -517,23 +458,20 @@ def test_letsencrypt_from_options(self):
mocked_prompt.assert_not_called()
def test_deployment_digitalocean(self):
- """Test setting a DigitalOcean deployment."""
- collector = Collector(deployment_type="digitalocean-k8s")
+ """Test that a cluster with digitalocean provider triggers set_digitalocean."""
+ collector = Collector(
+ cluster_core_providers={"dev": ["digitalocean"], "main": ["digitalocean"]}
+ )
collector.set_digitalocean = mock.MagicMock()
collector.set_deployment()
collector.set_digitalocean.assert_called_once()
- def test_deployment_other_k8s(self):
- """Test setting a generic Kubernetes deployment."""
- collector = Collector(deployment_type="other-k8s")
- collector.set_kubernetes = mock.MagicMock()
+ def test_deployment_aws_only(self):
+ """Test that a cluster without digitalocean does not trigger set_digitalocean."""
+ collector = Collector(cluster_core_providers={"main": ["aws"]})
+ collector.set_digitalocean = mock.MagicMock()
collector.set_deployment()
- collector.set_kubernetes.assert_called_once()
-
- def test_deployment_invalid(self):
- """Test setting an invalid deployment."""
- collector = Collector(deployment_type="invalid-deployment")
- self.assertRaises(ValueError, collector.set_deployment)
+ collector.set_digitalocean.assert_not_called()
def test_digitalocean_default(self):
"""Test setting the Digitalocean options from default."""
@@ -651,75 +589,6 @@ def test_digitalocean_token_options(self):
collector.set_digitalocean_token()
self.assertEqual(collector.digitalocean_token, "options-token")
- def test_kubernetes_input_redis(self):
- """Test setting Kubernets options from input with redis."""
- collector = Collector(use_redis=True)
- collector.set_digitalocean_token = mock.MagicMock()
- self.assertFalse(collector._other_kubernetes_enabled)
- self.assertIsNone(collector.kubernetes_cluster_ca_certificate)
- self.assertIsNone(collector.kubernetes_host)
- self.assertIsNone(collector.kubernetes_token)
- self.assertIsNone(collector.postgres_image)
- self.assertIsNone(collector.postgres_persistent_volume_capacity)
- self.assertIsNone(collector.postgres_persistent_volume_claim_capacity)
- self.assertIsNone(collector.postgres_persistent_volume_host_path)
- certificate_path = str(BASE_DIR / "tests/fake_certificate")
- with mock_input(
- certificate_path,
- "https://www.google.com",
- {"hidden": "toKenl0ngeR!"},
- "",
- "",
- "persistent/host/path",
- "",
- ):
- collector.set_kubernetes()
- self.assertTrue(collector._other_kubernetes_enabled)
- self.assertEqual(collector.kubernetes_cluster_ca_certificate, certificate_path)
- self.assertEqual(collector.kubernetes_host, "https://www.google.com")
- self.assertEqual(collector.kubernetes_token, "toKenl0ngeR!")
- self.assertEqual(collector.postgres_image, "postgres:14")
- self.assertEqual(collector.postgres_persistent_volume_capacity, "10Gi")
- self.assertEqual(collector.postgres_persistent_volume_claim_capacity, "")
- self.assertEqual(
- collector.postgres_persistent_volume_host_path, "persistent/host/path"
- )
- self.assertEqual(collector.redis_image, "redis:6.2")
-
- def test_kubernetes_input_no_redis(self):
- """Test setting Kubernets options from input without redis."""
- collector = Collector(use_redis=False)
- collector.set_digitalocean_token = mock.MagicMock()
- self.assertFalse(collector._other_kubernetes_enabled)
- self.assertIsNone(collector.kubernetes_cluster_ca_certificate)
- self.assertIsNone(collector.kubernetes_host)
- self.assertIsNone(collector.kubernetes_token)
- self.assertIsNone(collector.postgres_image)
- self.assertIsNone(collector.postgres_persistent_volume_capacity)
- self.assertIsNone(collector.postgres_persistent_volume_claim_capacity)
- self.assertIsNone(collector.postgres_persistent_volume_host_path)
- certificate_path = str(BASE_DIR / "tests/fake_certificate")
- with mock_input(
- certificate_path,
- "https://www.google.com",
- {"hidden": "toKenl0ngeR!"},
- "",
- "",
- "persistent/host/path",
- ):
- collector.set_kubernetes()
- self.assertTrue(collector._other_kubernetes_enabled)
- self.assertEqual(collector.kubernetes_cluster_ca_certificate, certificate_path)
- self.assertEqual(collector.kubernetes_host, "https://www.google.com")
- self.assertEqual(collector.kubernetes_token, "toKenl0ngeR!")
- self.assertEqual(collector.postgres_image, "postgres:14")
- self.assertEqual(collector.postgres_persistent_volume_capacity, "10Gi")
- self.assertEqual(collector.postgres_persistent_volume_claim_capacity, "")
- self.assertEqual(
- collector.postgres_persistent_volume_host_path, "persistent/host/path"
- )
- self.assertEqual(collector.redis_image, "")
-
def test_sentry_no(self):
"""Test not setting Sentry."""
collector = Collector(
@@ -1062,8 +931,6 @@ def test_get_runner(self):
"""Test getting the runner."""
collector = Collector(
backend_type="django",
- deployment_type="digitalocean-k8s",
- environments_distribution="1",
frontend_type="nextjs",
media_storage="local",
project_dirname="project_dirname",
@@ -1078,8 +945,6 @@ def test_get_runner(self):
collector._service_dir = Path(".")
runner = collector.get_runner()
self.assertEqual(runner.backend_type, "django")
- self.assertEqual(runner.deployment_type, "digitalocean-k8s")
- self.assertEqual(runner.environments_distribution, "1")
self.assertEqual(runner.frontend_type, "nextjs")
self.assertEqual(runner.media_storage, "local")
self.assertEqual(runner.project_dirname, "project_dirname")
@@ -1103,8 +968,8 @@ def test_collect(self):
collector.set_use_redis = mock.MagicMock()
collector.set_terraform = mock.MagicMock()
collector.set_vault = mock.MagicMock()
- collector.set_deployment_type = mock.MagicMock()
- collector.set_environments_distribution = mock.MagicMock()
+ collector.set_clusters = mock.MagicMock()
+ collector.set_envs = mock.MagicMock()
collector.set_domain_and_urls = mock.MagicMock()
collector.set_letsencrypt = mock.MagicMock()
collector.set_deployment = mock.MagicMock()
@@ -1122,8 +987,8 @@ def test_collect(self):
collector.set_use_redis.assert_called_once()
collector.set_terraform.assert_called_once()
collector.set_vault.assert_called_once()
- collector.set_deployment_type.assert_called_once()
- collector.set_environments_distribution.assert_called_once()
+ collector.set_clusters.assert_called_once()
+ collector.set_envs.assert_called_once()
collector.set_domain_and_urls.assert_called_once()
collector.set_letsencrypt.assert_called_once()
collector.set_deployment.assert_called_once()
diff --git a/{{cookiecutter.project_dirname}}/.gitignore b/{{cookiecutter.project_dirname}}/.gitignore
index 2ef8c738..f9732679 100644
--- a/{{cookiecutter.project_dirname}}/.gitignore
+++ b/{{cookiecutter.project_dirname}}/.gitignore
@@ -1,47 +1,3 @@
-# START https://raw.githubusercontent.com/github/gitignore/main/Terraform.gitignore
-
-# Local .terraform directories
-**/.terraform/*
-
-# .tfstate files
-*.tfstate
-*.tfstate.*
-
-# Crash log files
-crash.log
-crash.*.log
-
-# Warning: Please do not modify the following settings under any circumstances,
-# as doing so will completely disrupt the functioning of Talos.
-
-# Exclude all .tfvars files, which are likely to contain sensitive data, such as
-# password, private keys, and other secrets. These should not be part of version
-# control as they are data points which are potentially sensitive and subject
-# to change depending on the environment.
-# *.tfvars
-# *.tfvars.json
-
-# Ignore override files as they are usually used to override resources locally and so
-# are not checked in
-override.tf
-override.tf.json
-*_override.tf
-*_override.tf.json
-
-# Include override files you do wish to add to version control using negated pattern
-# !example_override.tf
-
-# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
-# example: *tfplan*
-
-# Ignore CLI configuration files
-.terraformrc
-terraform.rc
-
-# END https://raw.githubusercontent.com/github/gitignore/main/Terraform.gitignore
-
-# Start local
-
# JetBrains
.idea/
@@ -77,9 +33,3 @@ kubeconfig.yaml
# Environments
.env
-
-# Exclude terraform.tfvars files, as they are chosen to be used only locally.
-terraform.tfvars
-terraform.tfvars.json
-
-# END local
diff --git a/{{cookiecutter.project_dirname}}/.gitlab-ci.yml b/{{cookiecutter.project_dirname}}/.gitlab-ci.yml
index 36315246..bf139d4c 100644
--- a/{{cookiecutter.project_dirname}}/.gitlab-ci.yml
+++ b/{{cookiecutter.project_dirname}}/.gitlab-ci.yml
@@ -1,285 +1,134 @@
+stages:
+ - core:plan
+ - core:apply
+ - kubernetes:plan
+ - kubernetes:apply
+
+variables:
+ GITLAB_TOFU_INIT_NO_RECONFIGURE: true
+ PROJECT_DIR: "${CI_PROJECT_DIR}"
+ PROJECT_SLUG: {{ cookiecutter.project_slug }}
+ TF_CLOUD_HOSTNAME: app.terraform.io
+ TF_CLOUD_ORGANIZATION: {{ cookiecutter.terraform_cloud_organization }}
+ TOFU_BACKEND: terraform-cloud
+ VAULT_ROLE: "platform-gitlab-job"
+ VAULT_SECRETS_PREFIX: "platforms/${CLUSTER}"
+
+include:
+ - component: ${CI_SERVER_FQDN}/components/opentofu/job-templates@{{ cookiecutter.opentofu_component_version }}
+ inputs:
+ version: {{ cookiecutter.opentofu_component_version }}
+ opentofu_version: {{ cookiecutter.opentofu_version }}
+
workflow:
rules:
- - if: $CI_PIPELINE_SOURCE != "web" && $CI_COMMIT_BRANCH != "main"
+ - if: $CLUSTER == null || $CLUSTER == ""
when: never
- - if: $ENABLED_ALL
- when: always
- - if: $ENABLED_STAGES =~ /(?:^|,)\s*{% if cookiecutter.deployment_type == "digitalocean-k8s" %}(?:base|cluster){% else %}cluster{% endif %}\s*(?:,|$)/i && $ENABLED_STACKS
- when: always
- - if: $ENABLED_STAGES =~ /(?:^|,)\s*environment\s*(?:,|$)/i && $ENABLED_ENVS
- when: always
-
-stages:{% if cookiecutter.deployment_type == "digitalocean-k8s" %}
- - Base{% endif %}
- - Cluster
- - Environment
-
-image:
- name: docker:20
+ - if: $CI_PIPELINE_SOURCE != "web" || $CI_COMMIT_BRANCH != "main"
+ when: never
+ - when: always
-services:
- - docker:20-dind
+before_script:
+ - export TF_CLI_ARGS_plan="${TF_CLI_ARGS_plan} ${TOFU_VAR_FILE_ARGS}"
-.terraform:{% if cookiecutter.use_vault == "true" %}
+.core:
+ parallel:
+ matrix:
+ - CORE_PROVIDER: ["aws", "digitalocean"]
id_tokens:
+ GITLAB_OIDC_TOKEN:
+ aud: https://gitlab.com
VAULT_ID_TOKEN:
- aud: ${VAULT_ADDR}{% endif %}
- script:
- - >
- docker run --rm
- -u `id -u`
- -v ${PWD}:${PWD}
- -w ${PWD}{% if cookiecutter.terraform_backend == "gitlab" %}
- -e CI_API_V4_URL
- -e CI_COMMIT_SHA
- -e CI_JOB_ID
- -e CI_JOB_STAGE
- -e CI_JOB_TOKEN
- -e CI_PROJECT_ID
- -e CI_PROJECT_NAME
- -e CI_PROJECT_NAMESPACE
- -e CI_PROJECT_PATH
- -e CI_PROJECT_URL{% endif %}
- -e ENV_SLUG
- -e PROJECT_DIR=${CI_PROJECT_DIR}
- -e PROJECT_SLUG={{ cookiecutter.project_slug }}
- -e STACK_SLUG
- -e TERRAFORM_BACKEND={{ cookiecutter.terraform_backend }}
- -e TERRAFORM_EXTRA_VAR_FILE
- -e TERRAFORM_VARS_DIR
- -e TF_ROOT{% if cookiecutter.terraform_backend == "gitlab" %}
- -e TF_STATE_NAME{% endif %}{% if cookiecutter.use_vault == "false" %}{% if cookiecutter.deployment_type == "digitalocean-k8s" %}
- -e TF_VAR_digitalocean_token="${DIGITALOCEAN_TOKEN}"{% elif cookiecutter.deployment_type == "other-k8s" %}
- -e TF_VAR_kubernetes_cluster_ca_certificate="${KUBERNETES_CLUSTER_CA_CERTIFICATE}"
- -e TF_VAR_kubernetes_host="${KUBERNETES_HOST}"
- -e TF_VAR_kubernetes_token="${KUBERNETES_TOKEN}"{% endif %}{% if "s3" in cookiecutter.media_storage %}
- -e TF_VAR_s3_access_id="${S3_ACCESS_ID}"
- -e TF_VAR_s3_bucket_name="${S3_BUCKET_NAME}"
- -e TF_VAR_s3_host="${S3_HOST}"
- -e TF_VAR_s3_region="${S3_REGION}"
- -e TF_VAR_s3_secret_key="${S3_SECRET_KEY}"{% endif %}
- -e TF_VAR_basic_auth_password="${BASIC_AUTH_PASSWORD}"
- -e TF_VAR_basic_auth_username="${BASIC_AUTH_USERNAME}"
- -e TF_VAR_grafana_password="${GRAFANA_PASSWORD}"
- -e TF_VAR_registry_password="${REGISTRY_PASSWORD}"
- -e TF_VAR_registry_username="${REGISTRY_USERNAME}"
- -e TF_VAR_tls_certificate_crt="${TLS_CERTIFICATE_CRT}"
- -e TF_VAR_tls_certificate_key="${TLS_CERTIFICATE_KEY}"{% endif %}{% if cookiecutter.terraform_backend != "gitlab" %}
- -e TF_WORKSPACE{% endif %}{% if cookiecutter.terraform_backend == "terraform-cloud" and cookiecutter.use_vault == "false" %}
- -e TFC_TOKEN{% endif %}{% if cookiecutter.use_vault == "true" %}
- -e VAULT_ADDR
- -e VAULT_ID_TOKEN
- -e VAULT_ROLE
- -e VAULT_SECRETS
- -e VAULT_SECRETS_PREFIX
- -e VAULT_VERSION{% endif %}
- registry.gitlab.com/gitlab-org/terraform-images/stable:latest sh -c "${TERRAFORM_CMD}"
-
-.init:
- extends:
- - .terraform
+ aud: ${VAULT_ADDR}
+ image: {{ cookiecutter.minos_platform_image }}
variables:
- TERRAFORM_CMD: ${CI_PROJECT_DIR}/scripts/deploy.sh init
+ AWS_WEB_IDENTITY_TOKEN_FILE: "/tmp/web_identity_token"
+ GITLAB_TOFU_ROOT_DIR: "${CI_PROJECT_DIR}/tofu/core/${CORE_PROVIDER}"
+ TF_WORKSPACE: "${PROJECT_SLUG}_platform_${CLUSTER}_core_${CORE_PROVIDER}"
+ TOFU_ROOT_PATH: "tofu/core/${CORE_PROVIDER}"
+ TOFU_VAR_FILES: "${CORE_PROVIDER}.tfvars"
+ TOFU_VARS_PATH: "minos/${CLUSTER}/core"
+ VAULT_SECRETS: ${CORE_PROVIDER}
+ rules:
+ - exists:
+ - minos/${CLUSTER}/core/${CORE_PROVIDER}.tfvars
+ when: manual
+ environment:
+ name: ${CLUSTER}
-.validate:
- extends:
- - .terraform
- variables:
- TERRAFORM_CMD: ${CI_PROJECT_DIR}/scripts/deploy.sh validate
+core:plan:
+ stage: core:plan
+ extends: [.opentofu:plan, .core]
-.plan:
- extends:
- - .terraform
+core:apply:
+ stage: core:apply
+ extends: [.opentofu:apply, .core]
+ needs:
+ - job: core:plan
variables:
- TERRAFORM_CMD: ${CI_PROJECT_DIR}/scripts/deploy.sh plan-json
+ TOFU_OUTPUTS_FILE: "${CI_PROJECT_DIR}/tofu/kubernetes/${CORE_PROVIDER}.auto.tfvars.json"
+ after_script:
+ - gitlab-tofu output -json | jq 'map_values(.value)' > "${TOFU_OUTPUTS_FILE}"
artifacts:
- name: plan
+ access: none
paths:
- - ${TF_ROOT}/plan.cache
- reports:
- terraform: ${TF_ROOT}/plan.json
-
-.apply:
- extends:
- - .terraform
- variables:
- TERRAFORM_CMD: ${CI_PROJECT_DIR}/scripts/deploy.sh apply -auto-approve
-
-{% if cookiecutter.deployment_type == "digitalocean-k8s" %}### BASE STAGE ###
+ - "${TOFU_OUTPUTS_FILE}"
+ expire_in: 1h
-.base_stage:
- stage: Base
- variables:
- TERRAFORM_VARS_DIR: ${CI_PROJECT_DIR}/terraform/base/vars{% if cookiecutter.use_vault == "true" %}
- VAULT_ROLE: orchestrator-stacks
- VAULT_SECRETS: "digitalocean s3"
- VAULT_SECRETS_PREFIX: "stacks/${CI_ENVIRONMENT_NAME}"{% endif %}{% for stack in cookiecutter.resources.stacks %}
-
-# {{ stack.slug|title }} Stack (Base Stage) #
-
-.stack_{{ stack.slug }}_base:
- extends:
- - .base_stage
+.kubernetes:
+ id_tokens:
+ VAULT_ID_TOKEN:
+ aud: ${VAULT_ADDR}
+ image: {{ cookiecutter.minos_platform_image }}
variables:
- STACK_SLUG: {{ stack.slug }}
- TERRAFORM_EXTRA_VAR_FILE: {{ stack.slug }}.tfvars
- TF_ROOT: ${CI_PROJECT_DIR}/terraform/base/{{ cookiecutter.deployment_type }}{% if cookiecutter.terraform_backend == "gitlab" %}
- TF_STATE_NAME: stack_{{ stack.slug }}_base{% else %}
- TF_WORKSPACE: {{ cookiecutter.project_slug }}_{{ cookiecutter.service_slug }}_base_{{ stack.slug }}{% endif %}
- cache:
- key: {{ cookiecutter.project_slug }}-stack-{{ stack.slug }}-base
- paths:
- - ${TF_ROOT}/.terraform
- environment:
- name: {{ stack.name }}
+ GITLAB_TOFU_ROOT_DIR: "${CI_PROJECT_DIR}/tofu/kubernetes"
+ TF_WORKSPACE: "${PROJECT_SLUG}_platform_${CLUSTER}_kubernetes"
+ TOFU_ROOT_PATH: tofu/kubernetes
+ TOFU_VAR_FILES: kubernetes.tfvars
+ TOFU_VARS_PATH: "minos/${CLUSTER}"
+ VAULT_SECRETS: "digitalocean"
rules:
- - if: $ENABLED_ALL == "true"
- when: always
- - if: $ENABLED_STAGES =~ /(?:^|,)\s*base\s*(?:,|$)/i && $ENABLED_STACKS =~ /(?:^|,)\s*{{ stack.slug }}\s*(?:,|$)/i
- when: always
- - when: never
-
-stack_{{ stack.slug }}_base_init:
- extends:
- - .init
- - .stack_{{ stack.slug }}_base
-
-stack_{{ stack.slug }}_base_validate:
- extends:
- - .validate
- - .stack_{{ stack.slug }}_base
- needs: ["stack_{{ stack.slug }}_base_init"]
-
-stack_{{ stack.slug }}_base_plan:
- extends:
- - .plan
- - .stack_{{ stack.slug }}_base
- needs: ["stack_{{ stack.slug }}_base_validate"]
-
-stack_{{ stack.slug }}_base_apply:
- extends:
- - .apply
- - .stack_{{ stack.slug }}_base
- needs: ["stack_{{ stack.slug }}_base_plan"]{% endfor %}
-
-{% endif %}### CLUSTER STAGE ###
-
-.cluster_stage:
- stage: Cluster
- variables:
- TERRAFORM_VARS_DIR: ${CI_PROJECT_DIR}/terraform/cluster/vars{% if cookiecutter.use_vault == "true" %}
- VAULT_ROLE: orchestrator-stacks
- VAULT_SECRETS: "digitalocean k8s"
- VAULT_SECRETS_PREFIX: "stacks/${CI_ENVIRONMENT_NAME}"{% endif %}{% for stack in cookiecutter.resources.stacks %}
-
-# {{ stack.slug|title }} Stack (Cluster Stage) #
-
-.stack_{{ stack.slug }}_cluster:
- extends:
- - .cluster_stage
- variables:
- STACK_SLUG: {{ stack.slug }}
- TERRAFORM_EXTRA_VAR_FILE: {{ stack.slug }}.tfvars
- TF_ROOT: ${CI_PROJECT_DIR}/terraform/cluster/{{ cookiecutter.deployment_type }}{% if cookiecutter.terraform_backend == "gitlab" %}
- TF_STATE_NAME: stack_{{ stack.slug }}_cluster{% else %}
- TF_WORKSPACE: {{ cookiecutter.project_slug }}_{{ cookiecutter.service_slug }}_cluster_{{ stack.slug }}{% endif %}
- cache:
- key: {{ cookiecutter.project_slug }}-stack-{{ stack.slug }}-cluster
- paths:
- - ${TF_ROOT}/.terraform
+ - &kubernetes-rule
+ exists:
+ - minos/${CLUSTER}/kubernetes.tfvars
+ when: manual
environment:
- name: {{ stack.name }}
- rules:
- - if: $ENABLED_ALL == "true"
- when: always
- - if: $ENABLED_STAGES =~ /(?:^|,)\s*cluster\s*(?:,|$)/i && $ENABLED_STACKS =~ /(?:^|,)\s*{{ stack.slug }}\s*(?:,|$)/i
- when: always
- - when: never
-
-stack_{{ stack.slug }}_cluster_init:
- extends:
- - .init
- - .stack_{{ stack.slug }}_cluster
-
-stack_{{ stack.slug }}_cluster_validate:
- extends:
- - .validate
- - .stack_{{ stack.slug }}_cluster
- needs: ["stack_{{ stack.slug }}_cluster_init"]
-
-stack_{{ stack.slug }}_cluster_plan:
- extends:
- - .plan
- - .stack_{{ stack.slug }}_cluster
- needs:{% if cookiecutter.deployment_type == "digitalocean-k8s" %}
- - job: stack_{{ stack.slug }}_base_apply
- optional: true{% endif %}
- - job: stack_{{ stack.slug }}_cluster_validate
-
-stack_{{ stack.slug }}_cluster_apply:
- extends:
- - .apply
- - .stack_{{ stack.slug }}_cluster
- needs: ["stack_{{ stack.slug }}_cluster_plan"]{% endfor %}
-
-### ENVIRONMENT STAGE ###
+ name: ${CLUSTER}
-.environment_stage:
- stage: Environment
- variables:
- TERRAFORM_VARS_DIR: ${CI_PROJECT_DIR}/terraform/environment/vars
- TF_ROOT: ${CI_PROJECT_DIR}/terraform/environment/{{ cookiecutter.deployment_type }}{% if cookiecutter.use_vault == "true" %}
- VAULT_ROLE: orchestrator-envs
- VAULT_SECRETS: "digitalocean k8s orchestrator/basic_auth orchestrator/regcred orchestrator/tls monitoring s3"
- VAULT_SECRETS_PREFIX: "envs/${CI_ENVIRONMENT_NAME}"{% endif %}{% for env in cookiecutter.resources.envs %}
-
-# {{ env.name|title }} Environment #
-
-.env_{{ env.slug }}:
- extends:
- - .environment_stage
- variables:
- ENV_SLUG: {{ env.slug }}
- STACK_SLUG: {{ env.stack_slug }}
- TERRAFORM_EXTRA_VAR_FILE: {{ env.slug }}.tfvars{% if cookiecutter.terraform_backend == "gitlab" %}
- TF_STATE_NAME: env_{{ env.slug }}{% else %}
- TF_WORKSPACE: {{ cookiecutter.project_slug }}_{{ cookiecutter.service_slug }}_environment_{{ env.slug }}{% endif %}
- cache:
- key: {{ cookiecutter.project_slug }}-env-{{ env.slug }}
- paths:
- - ${TF_ROOT}/.terraform
- environment:
- name: {{ env.name }}
- url: {{ env.url }}
+.kubernetes:plan:
+ stage: kubernetes:plan
+ extends: [.opentofu:plan, .kubernetes]
+ needs:
+ - job: core:apply
+ artifacts: true
+ parallel:
+ matrix:
+ - CORE_PROVIDER: [aws, digitalocean]
+
+kubernetes-base:plan:
+ extends: [.kubernetes:plan]
rules:
- - if: $ENABLED_ALL == "true"
- when: always
- - if: $ENABLED_STAGES =~ /(?:^|,)\s*environment\s*(?:,|$)/i && $ENABLED_ENVS =~ /(?:^|,)\s*{{ env.slug }}\s*(?:,|$)/i
- when: always
- - when: never
-
-env_{{ env.slug }}_init:
- extends:
- - .init
- - .env_{{ env.slug }}
-
-env_{{ env.slug }}_validate:
- extends:
- - .validate
- - .env_{{ env.slug }}
- needs: ["env_{{ env.slug }}_init"]
-
-env_{{ env.slug }}_plan:
- extends:
- - .plan
- - .env_{{ env.slug }}
+ - <<: *kubernetes-rule
+ variables:
+ TF_CLI_ARGS_plan: >
+ -target=helm_release.traefik
+ -target=helm_release.cert_manager
+
+kubernetes-full:plan:
+ stage: kubernetes:plan
+ extends: [.kubernetes:plan]
+
+.kubernetes:apply:
+ stage: kubernetes:apply
+ extends: [.opentofu:apply, .kubernetes]
+ allow_failure: true
+
+kubernetes-base:apply:
+ extends: [.kubernetes:apply]
needs:
- - job: stack_{{ env.stack_slug }}_cluster_apply
- optional: true
- - job: env_{{ env.slug }}_validate
+ - job: kubernetes-base:plan
-env_{{ env.slug }}_apply:
- extends:
- - .apply
- - .env_{{ env.slug }}
- needs: ["env_{{ env.slug }}_plan"]{% endfor %}
+kubernetes-full:apply:
+ extends: [.kubernetes:apply]
+ needs:
+ - job: kubernetes-full:plan
diff --git a/{{cookiecutter.project_dirname}}/README.md b/{{cookiecutter.project_dirname}}/README.md
index 6e5bad3d..153e29ff 100644
--- a/{{cookiecutter.project_dirname}}/README.md
+++ b/{{cookiecutter.project_dirname}}/README.md
@@ -1,190 +1,102 @@
-# {{ cookiecutter.project_name }}
+# {{ cookiecutter.project_name }}
-This is the "{{ cookiecutter.project_name }}" {{ cookiecutter.service_slug }}.
+Platform repository for the **{{ cookiecutter.project_name }}** project.
-## Index
+This repo orchestrates the cloud infrastructure (Kubernetes cluster, core providers,
+DNS, certificates, …) via the [Minos](https://gitlab.com/20tab-open/minos) platform
+image. The application services live as sibling repositories cloned as nested
+directories with their own `.git`.
-- [Provisioning](#provisioning)
- - [Stages](#stages)
- - [Stacks](#stacks)
- - [Environments](#stage)
-- [Quickstart](#quickstart)
- - [Git](#git)
- - [Clone](#clone)
- - [Environment variables](#environment-variables)
- - [Docker](#docker)
- - [Build](#build)
- - [Run](#run)
- - [Makefile shortcuts](#makefile-shortcuts)
- - [Pull](#pull)
- - [Django manage command](#django-manage-command)
- - [Restart and build services](#restart-and-build-services)
- - [Create SSL Certificate 1](#create-ssl-certificate-sup-ida-setup-https-locally1sup)
- - [Create and activate a local SSL Certificate 1](#create-and-activate-a-local-ssl-certificate-sup-ida-setup-https-locally1sup)
- - [Install the cert utils](#install-the-cert-utils)
- - [Import certificates](#import-certificates)
- - [Trust the self-signed server certificate](#trust-the-self-signed-server-certificate)
+## Layout
-## Provisioning
-
-The first run is manual, made from [GitLab Pipeline](https://gitlab.com/{{ cookiecutter.project_slug }}/{{ cookiecutter.service_slug }}/-/pipelines/new).
-
-To create all the terraform resources, run the pipeline with the following variable:
-
-`ENABLED_ALL`= `true`
-
-If you want to choose what to activate to limit any costs, read below.
-
-### Stages
-
-{% if cookiecutter.deployment_type == "digitalocean-k8s" %}Base stage will create Kubernetes Cluster{% if cookiecutter.media_storage == "digitalocean-s3" %}, S3 Spaces{% endif %} and Databases Cluster.
-{% endif %}Cluster stage will create Ingress, Certificate and Monitoring if enabled.
-Environment stage will create the other resource for each of it.
-
-| Value | Description |
-| ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
-| {% if cookiecutter.deployment_type == "digitalocean-k8s" %}`base` | Base stage will create Kubernetes Cluster{% if cookiecutter.media_storage == "digitalocean-s3" %}, S3 Spaces{% endif %} and Databases Cluster |
-| {% endif %}`cluster` | Cluster stage will create Ingress, Certificate and Monitoring if enabled. |
-| `environment` | Environment stage will create the other resource for each of it. |
-
-`ENABLED_STAGES` = `{% if cookiecutter.deployment_type == "digitalocean-k8s" %}base, {% endif %}cluster, environment`
-
-### Stacks
-
-{% if cookiecutter.environments_distribution == "1" %}You have opted to have all environments share the same stack.
-
-`ENABLED_STACKS` = `main`{% endif %}{% if cookiecutter.environments_distribution == "2" %}You have opted to have Dev and Stage environments share the same stack, Prod has its own.
-
-`ENABLED_STACKS` = `dev, main`{% endif %}{% if cookiecutter.environments_distribution == "3" %}Each environment has its own stack
-
-`ENABLED_STACKS` = `main, dev, prod`{% endif %}
-
-### Environments
-
-| Value | Description |
-| ------- | ------------------------------------------------------------------------ |
-| `dev` | Development is the first delivery environment for developers. |
-| `stage` | Staging is the test environment for customers. |
-| `prod` | Production is the public production environment accessible to all users. |
-
-`ENABLED_ENVS` = `dev, stage, prod`
-
-## Quickstart
-
-This section explains the steps you need to clone and work with this project.
-
-1. [clone](#clone) the project code
-2. set all the required [environment variables](#environment-variables)
-3. [build](#build) all the services
-4. [create a superuser](#create-a-superuser) to login the platform
-5. [run](#run) all the services
-6. login using the URL: http://localhost:8080
-
-### Git
-
-#### Clone
-
-Clone the {{ cookiecutter.service_slug }} and services repositories:
-
-```console
-git clone __VCS_BASE_SSH_URL__/{{ cookiecutter.service_slug }}.git {{ cookiecutter.project_dirname }}
-cd {{ cookiecutter.project_dirname }}{% if cookiecutter.backend_type != 'none' %}
-git clone -b develop __VCS_BASE_SSH_URL__/{{ cookiecutter.backend_service_slug }}.git{% endif %}{% if cookiecutter.frontend_type != 'none' %}
-git clone -b develop __VCS_BASE_SSH_URL__/{{ cookiecutter.frontend_service_slug }}.git{% endif %}
-cd ..
```
-
-**NOTE** : We're cloning the `develop` branch for all repo.
-
-### Environment variables
-
-In order for the project to run correctly, a number of environment variables must be set in an `.env` file inside the {{ cookiecutter.service_slug }} directory. For ease of use, a `.env_template` template is provided.
-
-Enter the newly created **project** directory and create the `.env` file copying from `.env_template`:
-
-```console
-$ cd ~/projects/{{ cookiecutter.project_dirname }}
-$ cp .env_template .env
+{{ cookiecutter.project_dirname }}/
+├── .gitlab-ci.yml # platform pipeline (core + kubernetes stages)
+├── minos/
+│ └── ${CLUSTER}/ # one folder per cluster (e.g. dev, main)
+│ ├── core/
+│ │ ├── aws.tfvars
+│ │ └── digitalocean.tfvars
+│ └── kubernetes.tfvars
+├── vault-project.tfvars.example # input for Phase A (admin) of vault-project
+{% if cookiecutter.backend_type != 'none' %}├── {{ cookiecutter.backend_service_slug }}/ # backend service (own .git)
+{% endif %}{% if cookiecutter.frontend_type != 'none' %}├── {{ cookiecutter.frontend_service_slug }}/ # frontend service (own .git)
+{% endif %}└── README.md
```
-### Docker
+The matrix in `.gitlab-ci.yml` runs core provisioning for **aws** and **digitalocean**
+in parallel; tweak `CORE_PROVIDER` matrix or remove tfvars for providers you don't use.
-All the following Docker commands are supposed to be run from the {{ cookiecutter.service_slug }} directory.
+## Prerequisites
-#### Build
+This is a two-phase setup. Phase A is run once by an administrator, Phase B is the
+GitLab pipeline that consumes the platform repo.
-```console
-$ docker-compose build
-```
+### Phase A — admin (one-off)
-#### Run
+The admin runs the [`vault-project`](https://gitlab.com/20tab/vault/vault-project)
+Terraform module to create per-project policies and identity entities on Vault. The
+file `vault-project.tfvars.example` in this repo contains the values to use:
-```console
-$ docker-compose up
+```shell
+git clone https://gitlab.com/20tab/vault/vault-project.git
+cd vault-project
+cp /path/to/this/repo/vault-project.tfvars.example terraform.tfvars
+# edit project_admin_users, project_namespace_path
+terraform init
+terraform apply
```
-**NOTE**: It can be daemonized adding the `-d` flag.
+After Phase A the admin also seeds the per-project Vault secrets used by the
+platform CI (DigitalOcean token, S3 credentials, TFC token).
-### Makefile shortcuts
+### Phase B — platform pipeline
-#### Self documentation of Makefile commands
+Required variables on the GitLab project (or the parent group):
-To show the Makefile self documentation help:
+| Variable | Notes |
+| -------------------------- | ------------------------------------------------------------------------- |
+| `VAULT_ADDR` | Vault address (e.g. `https://vault.20tab.com/`). |
+| `TF_CLOUD_HOSTNAME` | Defaults to `app.terraform.io`. |
+| `TF_CLOUD_ORGANIZATION` | Set to `{{ cookiecutter.terraform_cloud_organization }}`. |
+| `CLUSTER` | Set on pipeline trigger (e.g. `dev`, `main`). |
-```console
-$ make
-```
-
-#### Pull
-
-Pull the main git repo and the sub-repos:
+To provision a cluster, run a manual pipeline on `main` from the GitLab UI
+(_Pipelines → Run pipeline_) with `CLUSTER=`. The pipeline iterates:
-```console
-$ make pull
-```
-
-#### Django manage command
+1. `core:plan` → `core:apply` (matrix on `CORE_PROVIDER`) reads
+ `minos/${CLUSTER}/core/${CORE_PROVIDER}.tfvars` and applies via the
+ `{{ cookiecutter.minos_platform_image }}` image.
+2. `kubernetes:plan` → `kubernetes:apply` reads `minos/${CLUSTER}/kubernetes.tfvars`
+ and consumes the outputs from `core:apply` via auto-loaded JSON tfvars.
-Use the Django `manage.py` command shell:
+Two kubernetes plan/apply variants are provided:
-```console
-$ make django
-```
+- `kubernetes-base` only applies `helm_release.traefik` and `helm_release.cert_manager`
+ (bootstrap of routing + certs).
+- `kubernetes-full` applies the full kubernetes stack.
-You can pass the specific command:
+The TFC workspace naming used:
-```console
-$ make django p=check
-```
-
-You can pass the container name:
-
-```console
-$ make django p=shell c=backend_2
-```
-
-#### Restart and build services
-
-Restart and build all services:
-
-```console
-$ make rebuild
-```
-
-You can pass the service name:
-
-```console
-$ make rebuild s=backend
-```
+- `${PROJECT_SLUG}_platform_${CLUSTER}_core_${CORE_PROVIDER}`
+- `${PROJECT_SLUG}_platform_${CLUSTER}_kubernetes`
-### Activate a valid local SSL Certificate
+All workspaces live inside the TFC project named after `{{ cookiecutter.project_slug }}`
+(execution mode `local`, inherited from the project).
-Import the `traefik/20tab.crt` file in your browser to have a trusted ssl certificate:
+## Services
-#### Firefox
+Application services live in sibling directories, each as an independent GitLab
+project with its own `.git`. They are bootstrapped from their own template repos
+(see `django-continuous-delivery`, `nextjs-continuous-delivery`) and consume the
+`{{ cookiecutter.minos_service_image }}` image at deploy time.
-- Settings > Privacy & Security > Manage Certificates > View Certificates... > Authorities > Import
+Each service repo lays out its own `minos/` directory with `common.tfvars` plus
+per-environment `this.tfvars` and `shared-config.yaml`. Service workspaces on TFC
+are named `${PROJECT_SLUG}_${SERVICE_SLUG}_${CI_ENVIRONMENT_SLUG}`.
-#### Chrome
+## References
-- Settings > Security > Certificates > Authorities > Import
+- Minos image registry: `registry.gitlab.com/20tab-open/minos/{platform,service}`
+- OpenTofu CI component: `${CI_SERVER_FQDN}/components/opentofu/job-templates@{{ cookiecutter.opentofu_component_version }}`
+- OpenTofu version: `{{ cookiecutter.opentofu_version }}`
diff --git a/{{cookiecutter.project_dirname}}/docker-compose.yaml b/{{cookiecutter.project_dirname}}/docker-compose.yaml
deleted file mode 100644
index 3acf48d3..00000000
--- a/{{cookiecutter.project_dirname}}/docker-compose.yaml
+++ /dev/null
@@ -1,101 +0,0 @@
-services:
-{% if cookiecutter.backend_type != "none" %}
- {{ cookiecutter.backend_service_slug }}:
- build:
- args:
- USER: ${USER:-appuser}
- context: ./{{ cookiecutter.backend_service_slug }}
- target: ${BACKEND_BUILD_TARGET:-local}
- depends_on:
- postgres:
- condition: service_healthy
- environment:
- - CACHE_URL
- - DATABASE_URL
- - DJANGO_ADMINS
- - DJANGO_ALLOWED_HOSTS
- - DJANGO_CONFIGURATION
- - DJANGO_DEBUG
- - DJANGO_DEFAULT_FROM_EMAIL
- - DJANGO_SECRET_KEY
- - DJANGO_SERVER_EMAIL
- - DJANGO_SESSION_COOKIE_DOMAIN
- - DJANGO_SUPERUSER_EMAIL
- - DJANGO_SUPERUSER_PASSWORD
- - DJANGO_SUPERUSER_USERNAME
- - EMAIL_URL{% if cookiecutter.use_pact == "true" %}
- - PACT_BROKER_URL
- - PACT_PROVIDER_NAME{% endif %}
- - PYTHONBREAKPOINT
- - PYTHONDEVMODE
- - PYTHONTRACEMALLOC
- healthcheck:
- test: curl --fail --head http://{{ cookiecutter.backend_service_slug }}:{{ cookiecutter.backend_service_port }}/{{ cookiecutter.backend_service_slug }}/health/ || exit 1
- interval: 30s
- start_period: 5s
- timeout: 5s
- retries: 5
- user: ${USER:-appuser}
- volumes:
- - ./{{ cookiecutter.backend_service_slug }}:/app{% endif %}
-{% if cookiecutter.frontend_type != "none" %}
- {{ cookiecutter.frontend_service_slug }}:
- build:
- args:
- GROUP_ID: ${GROUP_ID:-1000}
- USER_ID: ${USER_ID:-1000}
- USER: ${USER:-appuser}
- context: ./{{ cookiecutter.frontend_service_slug }}
- dockerfile: ${FRONTEND_DOCKER_FILE:-docker/local.Dockerfile}
- environment:
- - INTERNAL_BACKEND_URL=${INTERNAL_BACKEND_URL:-http://{{ cookiecutter.backend_service_slug }}:{{ cookiecutter.backend_service_port }}{{ '}' }}
- - NEXT_PUBLIC_PROJECT_URL=${PROJECT_URL:-https://localhost:8443}
- - REACT_ENVIRONMENT=${REACT_ENVIRONMENT:-Development}
- healthcheck:
- test: wget -O- -q http://{{ cookiecutter.frontend_service_slug }}:{{ cookiecutter.frontend_service_port }}/{{ cookiecutter.frontend_service_slug }}/health || exit 1
- interval: 30s
- timeout: 5s
- start_period: 5s
- retries: 5
- user: ${USER:-appuser}
- volumes:
- - ./{{ cookiecutter.frontend_service_slug }}:/app
- - /app/node_modules{% endif %}
-
- traefik:
- command:
- - "--configFile=/traefik/conf/static.yaml"
- depends_on:{% if cookiecutter.backend_type != 'none' %}
- {{ cookiecutter.backend_service_slug }}:
- condition: service_healthy{% endif %}{% if cookiecutter.frontend_type != 'none' %}
- {{ cookiecutter.frontend_service_slug }}:
- condition: service_healthy{% endif %}
- healthcheck:
- test: ["CMD", "traefik", "healthcheck", "--ping"]
- interval: 30s
- timeout: 5s
- start_period: 5s
- retries: 5
- image: traefik:v2.10
- ports:
- - "${LOCAL_HTTPS_PORT:-8443}:8443"
- - "${TRAEFIK_DASHBOARD_PORT:-8080}:8080"
- volumes:
- - ./traefik/:/traefik/:ro
-
- postgres:
- environment:
- - POSTGRES_DB={{ cookiecutter.project_slug }}
- - POSTGRES_INITDB_ARGS=--no-sync
- - POSTGRES_PASSWORD=postgres
- healthcheck:
- test: ["CMD", "pg_isready", "-U", "postgres"]
- interval: 3s
- timeout: 3s
- retries: 30
- image: postgres:14-bullseye
- volumes:
- - pg_data:/var/lib/postgresql/data
-
-volumes:
- pg_data: {}
diff --git a/{{cookiecutter.project_dirname}}/scripts/deploy.sh b/{{cookiecutter.project_dirname}}/scripts/deploy.sh
deleted file mode 100755
index ccd7316d..00000000
--- a/{{cookiecutter.project_dirname}}/scripts/deploy.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env sh
-
-set -e
-
-# init.sh must be sourced to let it export env vars
-. "${PROJECT_DIR}"/scripts/deploy/init.sh
-
-sh "${PROJECT_DIR}"/scripts/deploy/terraform.sh "${@}"
diff --git a/{{cookiecutter.project_dirname}}/scripts/deploy/gitlab.sh b/{{cookiecutter.project_dirname}}/scripts/deploy/gitlab.sh
deleted file mode 100755
index 69a45af0..00000000
--- a/{{cookiecutter.project_dirname}}/scripts/deploy/gitlab.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env sh
-
-set -e
-
-# If TF_USERNAME is unset then default to GITLAB_USER_LOGIN
-TF_USERNAME="${TF_USERNAME:-${GITLAB_USER_LOGIN}}"
-# If TF_PASSWORD is unset then default to gitlab-ci-token/CI_JOB_TOKEN
-if [ -z "${TF_PASSWORD}" ]; then
-TF_USERNAME="gitlab-ci-token"
-TF_PASSWORD="${CI_JOB_TOKEN}"
-fi
-# If TF_ADDRESS is unset but TF_STATE_NAME is provided, then default to GitLab backend in current project
-if [ -n "${TF_STATE_NAME}" ]; then
-TF_ADDRESS="${TF_ADDRESS:-${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE_NAME}}"
-fi
-# Set variables for the HTTP backend to default to TF_* values
-export TF_HTTP_ADDRESS="${TF_HTTP_ADDRESS:-${TF_ADDRESS}}"
-export TF_HTTP_LOCK_ADDRESS="${TF_HTTP_LOCK_ADDRESS:-${TF_ADDRESS}/lock}"
-export TF_HTTP_LOCK_METHOD="${TF_HTTP_LOCK_METHOD:-POST}"
-export TF_HTTP_UNLOCK_ADDRESS="${TF_HTTP_UNLOCK_ADDRESS:-${TF_ADDRESS}/lock}"
-export TF_HTTP_UNLOCK_METHOD="${TF_HTTP_UNLOCK_METHOD:-DELETE}"
-export TF_HTTP_USERNAME="${TF_HTTP_USERNAME:-${TF_USERNAME}}"
-export TF_HTTP_PASSWORD="${TF_HTTP_PASSWORD:-${TF_PASSWORD}}"
-export TF_HTTP_RETRY_WAIT_MIN="${TF_HTTP_RETRY_WAIT_MIN:-5}"
-# Expose Gitlab specific variables to terraform since no -tf-var is available
-# Usable in the .tf file as variable "CI_JOB_ID" { type = string } etc
-export TF_VAR_CI_JOB_ID="${TF_VAR_CI_JOB_ID:-${CI_JOB_ID}}"
-export TF_VAR_CI_COMMIT_SHA="${TF_VAR_CI_COMMIT_SHA:-${CI_COMMIT_SHA}}"
-export TF_VAR_CI_JOB_STAGE="${TF_VAR_CI_JOB_STAGE:-${CI_JOB_STAGE}}"
-export TF_VAR_CI_PROJECT_ID="${TF_VAR_CI_PROJECT_ID:-${CI_PROJECT_ID}}"
-export TF_VAR_CI_PROJECT_NAME="${TF_VAR_CI_PROJECT_NAME:-${CI_PROJECT_NAME}}"
-export TF_VAR_CI_PROJECT_NAMESPACE="${TF_VAR_CI_PROJECT_NAMESPACE:-${CI_PROJECT_NAMESPACE}}"
-export TF_VAR_CI_PROJECT_PATH="${TF_VAR_CI_PROJECT_PATH:-${CI_PROJECT_PATH}}"
-export TF_VAR_CI_PROJECT_URL="${TF_VAR_CI_PROJECT_URL:-${CI_PROJECT_URL}}"
diff --git a/{{cookiecutter.project_dirname}}/scripts/deploy/init.sh b/{{cookiecutter.project_dirname}}/scripts/deploy/init.sh
deleted file mode 100755
index c5902c69..00000000
--- a/{{cookiecutter.project_dirname}}/scripts/deploy/init.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/env sh
-
-set -e
-
-export TF_VAR_env_slug="${ENV_SLUG}"
-export TF_VAR_project_slug="${PROJECT_SLUG}"
-export TF_VAR_stack_slug="${STACK_SLUG}"
-
-terraform_cli_args="-var-file=${TERRAFORM_VARS_DIR%/}/.tfvars"
-
-if [ "${TERRAFORM_EXTRA_VAR_FILE}" != "" ]; then
- extra_var_file="${TERRAFORM_VARS_DIR%/}/${TERRAFORM_EXTRA_VAR_FILE}"
- touch "${extra_var_file}"
- terraform_cli_args="${terraform_cli_args} -var-file=${extra_var_file}"
-fi
-
-if [ "${VAULT_ADDR}" != "" ]; then
- . "${PROJECT_DIR}"/scripts/deploy/vault.sh
- terraform_cli_args="${terraform_cli_args} -var-file=${TERRAFORM_VARS_DIR%/}/vault-secrets.tfvars.json"
-fi
-
-export TF_CLI_ARGS_destroy="${terraform_cli_args}"
-export TF_CLI_ARGS_plan="${terraform_cli_args}"
-
-case "${TERRAFORM_BACKEND}" in
- "gitlab")
- . "${PROJECT_DIR}"/scripts/deploy/gitlab.sh
- ;;
- "terraform-cloud")
- . "${PROJECT_DIR}"/scripts/deploy/terraform-cloud.sh
- ;;
-esac
diff --git a/{{cookiecutter.project_dirname}}/scripts/deploy/terraform-cloud.sh b/{{cookiecutter.project_dirname}}/scripts/deploy/terraform-cloud.sh
deleted file mode 100755
index b01a928f..00000000
--- a/{{cookiecutter.project_dirname}}/scripts/deploy/terraform-cloud.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env sh
-
-set -e
-
-export TF_CLI_CONFIG_FILE="${TF_ROOT}/cloud.tfc"
-cat << EOF > "${TF_CLI_CONFIG_FILE}"
-{
- "credentials": {
- "app.terraform.io": {
- "token": "${TFC_TOKEN}"
- }
- }
-}
-EOF
diff --git a/{{cookiecutter.project_dirname}}/scripts/deploy/terraform.sh b/{{cookiecutter.project_dirname}}/scripts/deploy/terraform.sh
deleted file mode 100755
index 43ac5c1e..00000000
--- a/{{cookiecutter.project_dirname}}/scripts/deploy/terraform.sh
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env sh
-
-set -e
-
-if [ "${DEBUG_OUTPUT}" = "true" ]; then
- set -x
-fi
-
-plan_cache="plan.cache"
-plan_json="plan.json"
-
-JQ_PLAN='
- (
- [.resource_changes[]?.change.actions?] | flatten
- ) | {
- "create":(map(select(.=="create")) | length),
- "update":(map(select(.=="update")) | length),
- "delete":(map(select(.=="delete")) | length)
- }
-'
-
-# Use terraform automation mode (will remove some verbose unneeded messages)
-export TF_IN_AUTOMATION=true
-
-init() {
- cd "${TF_ROOT}"
- if [ "${TERRAFORM_BACKEND}" = "terraform-cloud" ]; then
- terraform init "${@}" -input=false
- else
- terraform init "${@}" -input=false -reconfigure
- fi
-}
-
-case "${1}" in
- "apply")
- init
- terraform "${@}" -input=false "${plan_cache}"
- ;;
- "destroy")
- init
- terraform "${@}" -auto-approve
- ;;
- "fmt")
- terraform "${@}" -check -diff -recursive
- ;;
- "init")
- # shift argument list „one to the left“ to not call 'terraform init init'
- shift
- init "${@}"
- ;;
- "plan")
- init
- terraform "${@}" -input=false -out="${plan_cache}"
- ;;
- "plan-json")
- init
- terraform plan -input=false -out="${plan_cache}"
- terraform show -json "${plan_cache}" | \
- jq -r "${JQ_PLAN}" \
- > "${plan_json}"
- ;;
- "validate")
- init -backend=false
- terraform "${@}"
- ;;
- *)
- terraform "${@}"
- ;;
-esac
diff --git a/{{cookiecutter.project_dirname}}/scripts/deploy/vault.sh b/{{cookiecutter.project_dirname}}/scripts/deploy/vault.sh
deleted file mode 100755
index 402802d8..00000000
--- a/{{cookiecutter.project_dirname}}/scripts/deploy/vault.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env sh
-
-set -e
-
-vault_token=$(curl --silent --request POST --data "role=${VAULT_ROLE}" --data "jwt=${VAULT_ID_TOKEN}" "${VAULT_ADDR%/}"/v1/auth/gitlab-jwt/login | jq -r .auth.client_token)
-
-secrets_data="{}"
-
-for secret_path in ${VAULT_SECRETS}
-do
- secret_data=$(curl --silent --header "X-Vault-Token: ${vault_token}" "${VAULT_ADDR%/}"/v1/"${PROJECT_SLUG}"/"${VAULT_SECRETS_PREFIX}"/"${secret_path}" | jq -r '.data // {}') || secret_data="{}"
- secrets_data=$(echo "${secrets_data}" | jq --argjson new_data "${secret_data}" '. * $new_data')
-done
-
-echo "${secrets_data}" > "${TERRAFORM_VARS_DIR%/}"/vault-secrets.tfvars.json
-
-if [ "${TERRAFORM_BACKEND}" = "terraform-cloud" ]; then
- TFC_TOKEN=$(curl --silent --header "X-Vault-Token: ${vault_token}" "${VAULT_ADDR%/}"/v1/"${PROJECT_SLUG}"-tfc/creds/default | jq -r .data.token)
- export TFC_TOKEN
-fi
diff --git a/{{cookiecutter.project_dirname}}/terraform/base/digitalocean-k8s/main.tf b/{{cookiecutter.project_dirname}}/terraform/base/digitalocean-k8s/main.tf
deleted file mode 100644
index 30cbd538..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/base/digitalocean-k8s/main.tf
+++ /dev/null
@@ -1,167 +0,0 @@
-locals {
- digitalocean_default_region = "fra1"
- digitalocean_regions = data.digitalocean_regions.main.regions[*].slug
-
- database_cluster_region = contains(
- local.digitalocean_regions,
- var.database_cluster_region
- ) ? var.database_cluster_region : local.digitalocean_default_region
-
- redis_cluster_region = contains(
- local.digitalocean_regions,
- var.redis_cluster_region
- ) ? var.redis_cluster_region : local.digitalocean_default_region
-
- k8s_cluster_region = contains(
- local.digitalocean_regions,
- var.k8s_cluster_region
- ) ? var.k8s_cluster_region : local.digitalocean_default_region
-
- resource_name_prefix = var.stack_slug == "main" ? var.project_slug : "${var.project_slug}-${var.stack_slug}"
-}
-
-terraform {
- required_providers {
- digitalocean = {
- source = "digitalocean/digitalocean"
- version = "~> 2.36"
- }
- }
-}
-
-/* Providers */
-
-provider "digitalocean" {
- token = var.digitalocean_token
-
- spaces_access_id = var.s3_access_id
- spaces_secret_key = var.s3_secret_key
-}
-
-/* Data Sources */
-
-data "digitalocean_kubernetes_versions" "main" {}
-
-data "digitalocean_sizes" "k8s" {
- filter {
- key = "vcpus"
- values = range(var.k8s_cluster_node_min_vcpus, var.k8s_cluster_node_max_vcpus)
- }
-
- filter {
- key = "memory"
- values = [for i in range(var.k8s_cluster_node_min_memory, var.k8s_cluster_node_max_memory) : i * 1024]
- }
-
- filter {
- key = "regions"
- values = [local.k8s_cluster_region]
- }
-
- sort {
- key = "price_monthly"
- direction = "asc"
- }
-}
-
-data "digitalocean_regions" "main" {
- filter {
- key = "available"
- values = ["true"]
- }
-}
-
-/* Kubernetes Cluster */
-
-resource "digitalocean_kubernetes_cluster" "main" {
- name = "${local.resource_name_prefix}-k8s-cluster"
- region = local.k8s_cluster_region
- version = coalesce(
- var.k8s_cluster_version,
- data.digitalocean_kubernetes_versions.main.latest_version
- )
- auto_upgrade = true
-
- node_pool {
- name = "${local.resource_name_prefix}-k8s-node"
- node_count = var.k8s_cluster_node_count
- size = contains(
- data.digitalocean_sizes.k8s.sizes[*].slug,
- var.k8s_cluster_node_size
- ) ? var.k8s_cluster_node_size : element(data.digitalocean_sizes.k8s.sizes, 0).slug
- }
-
- maintenance_policy {
- start_time = "02:00"
- day = "sunday"
- }
-
- timeouts {
- create = "60m"
- }
-}
-
-/* Spaces Bucket */
-
-resource "digitalocean_spaces_bucket" "main" {
- count = var.create_s3_bucket ? 1 : 0
-
- name = "${local.resource_name_prefix}-s3-bucket"
- region = contains(
- local.digitalocean_regions,
- var.s3_region
- ) ? var.s3_region : local.digitalocean_default_region
-}
-
-/* Postgres Cluster */
-
-resource "digitalocean_database_cluster" "postgres" {
- name = "${local.resource_name_prefix}-database-cluster"
- region = local.database_cluster_region
- engine = var.database_cluster_engine
- version = var.database_cluster_version
- size = var.database_cluster_node_size
- node_count = var.database_cluster_node_count
-
- maintenance_window {
- day = "sunday"
- hour = "02:00"
- }
-
- timeouts {
- create = "60m"
- }
-}
-
-resource "digitalocean_database_firewall" "postgres" {
- cluster_id = digitalocean_database_cluster.postgres.id
-
- rule {
- type = "k8s"
- value = digitalocean_kubernetes_cluster.main.id
- }
-}
-
-/* Redis Cluster */
-
-resource "digitalocean_database_cluster" "redis" {
- count = var.use_redis ? 1 : 0
-
- name = "${local.resource_name_prefix}-redis-cluster"
- region = local.redis_cluster_region
- engine = "redis"
- version = var.redis_cluster_version
- size = var.redis_cluster_node_size
- node_count = var.redis_cluster_node_count
-}
-
-resource "digitalocean_database_firewall" "redis" {
- count = var.use_redis ? 1 : 0
-
- cluster_id = digitalocean_database_cluster.redis[0].id
-
- rule {
- type = "k8s"
- value = digitalocean_kubernetes_cluster.main.id
- }
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/base/digitalocean-k8s/variables.tf b/{{cookiecutter.project_dirname}}/terraform/base/digitalocean-k8s/variables.tf
deleted file mode 100644
index 131ccb14..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/base/digitalocean-k8s/variables.tf
+++ /dev/null
@@ -1,148 +0,0 @@
-variable "create_s3_bucket" {
- description = "Tell if a DigitalOcean Spaces bucket should be created."
- type = bool
- default = false
-}
-
-variable "database_cluster_engine" {
- description = "The DigitalOcean database cluster engine."
- type = string
- default = "pg"
-}
-
-variable "database_cluster_node_count" {
- description = "The DigitalOcean database cluster nodes count."
- type = number
- default = 1
-}
-
-variable "database_cluster_node_size" {
- description = "The DigitalOcean database cluster droplet size."
- type = string
-}
-
-variable "database_cluster_region" {
- description = "The DigitalOcean database cluster region."
- type = string
- default = ""
-}
-
-variable "database_cluster_version" {
- description = "The DigitalOcean database cluster major version."
- type = string
- default = "14"
-}
-
-variable "digitalocean_token" {
- description = "The DigitalOcean access token."
- type = string
- sensitive = true
-}
-
-variable "k8s_cluster_region" {
- description = "The DigitalOcean Kubernetes cluster region."
- type = string
- default = ""
-}
-
-variable "k8s_cluster_node_count" {
- description = "The DigitalOcean Kubernetes nodes count."
- type = number
- default = 1
-}
-
-variable "k8s_cluster_node_min_memory" {
- description = "The DigitalOcean Kubernetes nodes candidate minimum memory (in GB)."
- type = number
- default = 2
-}
-
-variable "k8s_cluster_node_min_vcpus" {
- description = "The DigitalOcean Kubernetes nodes candidate minimum number of vCPUs."
- type = number
- default = 1
-}
-
-variable "k8s_cluster_node_max_memory" {
- description = "The DigitalOcean Kubernetes nodes candidate maximum memory (in GB)."
- type = number
- default = 256
-}
-
-variable "k8s_cluster_node_max_vcpus" {
- description = "The DigitalOcean Kubernetes nodes candidate maximum number of vCPUs."
- type = number
- default = 48
-}
-
-variable "k8s_cluster_node_size" {
- description = "The DigitalOcean Kubernetes node size."
- type = string
- default = ""
-}
-
-variable "k8s_cluster_version" {
- description = "The DigitalOcean Kubernetes cluster version."
- type = string
- default = ""
-}
-
-variable "project_slug" {
- description = "The project slug."
- type = string
-}
-
-variable "redis_cluster_node_count" {
- description = "The DigitalOcean Redis cluster nodes count."
- type = number
- default = 1
-}
-
-variable "redis_cluster_node_size" {
- description = "The DigitalOcean Redis cluster droplet size."
- type = string
- default = ""
-}
-
-variable "redis_cluster_region" {
- description = "The DigitalOcean Redis cluster region."
- type = string
- default = ""
-}
-
-variable "redis_cluster_version" {
- description = "The DigitalOcean Redis cluster major version."
- type = string
- default = "7"
-}
-
-variable "s3_access_id" {
- description = "The S3 bucket access key ID."
- type = string
- sensitive = true
- default = ""
-}
-
-variable "s3_region" {
- description = "The S3 bucket region."
- type = string
- default = ""
-}
-
-variable "s3_secret_key" {
- description = "The S3 bucket secret access key."
- type = string
- sensitive = true
- default = ""
-}
-
-variable "stack_slug" {
- description = "The stack slug (e.g. 'main')."
- type = string
-}
-
-variable "use_redis" {
- description = "Tell if a Redis service is used."
- type = bool
- default = false
-}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/base/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/base/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}"
deleted file mode 100644
index 4ca44e9b..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/base/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}"
+++ /dev/null
@@ -1,4 +0,0 @@
-terraform {
- backend "http" {
- }
-}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/base/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/base/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}"
deleted file mode 100644
index 3849a361..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/base/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}"
+++ /dev/null
@@ -1,9 +0,0 @@
-terraform {
- cloud {
- organization = "{{ cookiecutter.terraform_cloud_organization }}"
-
- workspaces {
- tags = ["project:{{ cookiecutter.project_slug }}"]
- }
- }
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/base/vars/.tfvars b/{{cookiecutter.project_dirname}}/terraform/base/vars/.tfvars
deleted file mode 100644
index 8c95211d..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/base/vars/.tfvars
+++ /dev/null
@@ -1,9 +0,0 @@
-{% if "base" in cookiecutter.tfvars %}{% for item in cookiecutter.tfvars.base|sort %}{{ item }}
-{% endfor %}{% endif %}# database_cluster_engine="pg"
-# database_cluster_node_count=1
-# database_cluster_version="14"
-# k8s_cluster_node_count=1
-# k8s_cluster_node_size=""
-# k8s_cluster_version=""
-# redis_cluster_node_count=1
-# redis_cluster_version="7"
diff --git "a/{{cookiecutter.project_dirname}}/terraform/base/vars/{% if \"base_dev\" in cookiecutter.tfvars %}dev.tfvars{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/base/vars/{% if \"base_dev\" in cookiecutter.tfvars %}dev.tfvars{% endif %}"
deleted file mode 100644
index 1d0875f0..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/base/vars/{% if \"base_dev\" in cookiecutter.tfvars %}dev.tfvars{% endif %}"
+++ /dev/null
@@ -1,2 +0,0 @@
-{% for item in cookiecutter.tfvars.base_dev|sort %}{{ item }}
-{% endfor %}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/base/vars/{% if \"base_main\" in cookiecutter.tfvars %}main.tfvars{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/base/vars/{% if \"base_main\" in cookiecutter.tfvars %}main.tfvars{% endif %}"
deleted file mode 100644
index 01ae2919..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/base/vars/{% if \"base_main\" in cookiecutter.tfvars %}main.tfvars{% endif %}"
+++ /dev/null
@@ -1,2 +0,0 @@
-{% for item in cookiecutter.tfvars.base_main|sort %}{{ item }}
-{% endfor %}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/base/vars/{% if \"base_stage\" in cookiecutter.tfvars %}stage.tfvars{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/base/vars/{% if \"base_stage\" in cookiecutter.tfvars %}stage.tfvars{% endif %}"
deleted file mode 100644
index 5c33fdbe..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/base/vars/{% if \"base_stage\" in cookiecutter.tfvars %}stage.tfvars{% endif %}"
+++ /dev/null
@@ -1,2 +0,0 @@
-{% for item in cookiecutter.tfvars.base_stage|sort %}{{ item }}
-{% endfor %}
diff --git a/{{cookiecutter.project_dirname}}/terraform/cluster/digitalocean-k8s/main.tf b/{{cookiecutter.project_dirname}}/terraform/cluster/digitalocean-k8s/main.tf
deleted file mode 100644
index b5e29998..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/cluster/digitalocean-k8s/main.tf
+++ /dev/null
@@ -1,76 +0,0 @@
-locals {
- resource_name_prefix = var.stack_slug == "main" ? var.project_slug : "${var.project_slug}-${var.stack_slug}"
-}
-
-terraform {
- required_providers {
- digitalocean = {
- source = "digitalocean/digitalocean"
- version = "~> 2.36"
- }
- helm = {
- source = "hashicorp/helm"
- version = "~> 2.12"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- version = "~> 2.27"
- }
- }
-}
-
-/* Providers */
-
-provider "digitalocean" {
- token = var.digitalocean_token
-}
-
-provider "helm" {
- kubernetes {
- host = data.digitalocean_kubernetes_cluster.main.endpoint
- token = data.digitalocean_kubernetes_cluster.main.kube_config[0].token
- cluster_ca_certificate = base64decode(
- data.digitalocean_kubernetes_cluster.main.kube_config[0].cluster_ca_certificate
- )
- }
-}
-
-provider "kubernetes" {
- host = data.digitalocean_kubernetes_cluster.main.endpoint
- token = data.digitalocean_kubernetes_cluster.main.kube_config[0].token
- cluster_ca_certificate = base64decode(
- data.digitalocean_kubernetes_cluster.main.kube_config[0].cluster_ca_certificate
- )
-}
-
-/* Data Sources */
-
-data "digitalocean_kubernetes_cluster" "main" {
- name = "${local.resource_name_prefix}-k8s-cluster"
-}
-
-/* Traefik */
-
-module "traefik" {
- source = "../modules/kubernetes/traefik"
-
- letsencrypt_certificate_email = var.letsencrypt_certificate_email
- load_balancer_annotations = {
- "service.beta.kubernetes.io/do-loadbalancer-name" = "${local.resource_name_prefix}-load-balancer"
- }
-}
-
-/* Reloader */
-
-resource "helm_release" "reloader" {
- name = "reloader"
- repository = "https://stakater.github.io/stakater-charts"
- chart = "reloader"
- version = "1.0.74"
-}
-
-/* Metrics */
-
-module "metrics" {
- source = "../modules/kubernetes/metrics"
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/cluster/digitalocean-k8s/variables.tf b/{{cookiecutter.project_dirname}}/terraform/cluster/digitalocean-k8s/variables.tf
deleted file mode 100644
index 30f45c61..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/cluster/digitalocean-k8s/variables.tf
+++ /dev/null
@@ -1,21 +0,0 @@
-variable "digitalocean_token" {
- description = "The DigitalOcean access token."
- type = string
- sensitive = true
-}
-
-variable "letsencrypt_certificate_email" {
- description = "The email used to issue the Let's Encrypt certificate."
- type = string
- default = ""
-}
-
-variable "project_slug" {
- description = "The project slug."
- type = string
-}
-
-variable "stack_slug" {
- description = "The stack slug (e.g. 'main')."
- type = string
-}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/cluster/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/cluster/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}"
deleted file mode 100644
index 4ca44e9b..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/cluster/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}"
+++ /dev/null
@@ -1,4 +0,0 @@
-terraform {
- backend "http" {
- }
-}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/cluster/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/cluster/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}"
deleted file mode 100644
index 3849a361..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/cluster/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}"
+++ /dev/null
@@ -1,9 +0,0 @@
-terraform {
- cloud {
- organization = "{{ cookiecutter.terraform_cloud_organization }}"
-
- workspaces {
- tags = ["project:{{ cookiecutter.project_slug }}"]
- }
- }
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/metrics/main.tf b/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/metrics/main.tf
deleted file mode 100644
index e90fc289..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/metrics/main.tf
+++ /dev/null
@@ -1,34 +0,0 @@
-terraform {
- required_providers {
- helm = {
- source = "hashicorp/helm"
- version = "~> 2.12"
- }
- }
-}
-
-/* Metrics Server */
-
-resource "helm_release" "metrics_server" {
- name = "metrics-server"
- repository = "https://kubernetes-sigs.github.io/metrics-server"
- chart = "metrics-server"
- version = "3.12.0"
-
- namespace = "metrics-server"
-
- create_namespace = true
-
- values = [file("${path.module}/metrics-server/values.yaml")]
-}
-
-/* Kube State Metrics */
-
-resource "helm_release" "kube_state_metrics" {
- name = "kube-state-metrics"
- repository = "https://charts.bitnami.com/bitnami"
- chart = "kube-state-metrics"
- version = "3.16.2"
-
- namespace = "kube-system"
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/metrics/metrics-server/values.yaml b/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/metrics/metrics-server/values.yaml
deleted file mode 100644
index 39600ebd..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/metrics/metrics-server/values.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-## Digitalocean k8s starter kit metrics-server
-## Ref: https://github.com/digitalocean/Kubernetes-Starter-Kit-Developers/blob/main/07-scaling-application-workloads/assets/manifests/metrics-server-values-v3.8.2.yaml
-
-replicas: 2
-
-apiService:
- create: true
-
-hostNetwork:
- enabled: false
diff --git a/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/traefik/main.tf b/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/traefik/main.tf
deleted file mode 100644
index 1c4ea749..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/traefik/main.tf
+++ /dev/null
@@ -1,56 +0,0 @@
-terraform {
- required_providers {
- helm = {
- source = "hashicorp/helm"
- version = "~> 2.12"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- version = "~> 2.27"
- }
- }
-}
-
-resource "helm_release" "traefik" {
- name = "traefik"
- repository = "https://traefik.github.io/charts"
- chart = "traefik"
- version = var.traefik_helm_chart_version
-
- namespace = "traefik"
- create_namespace = true
-
- timeout = 900
-
- values = [
- file("${path.module}/values.yaml"),
- yamlencode(
- {
- service = {
- enabled = "true"
- type = "LoadBalancer"
- annotations = var.load_balancer_annotations
- }
- }
- )
- ]
-}
-
-/* Cert Manager */
-
-resource "helm_release" "cert_manager" {
- count = var.letsencrypt_certificate_email != "" ? 1 : 0
-
- name = "cert-manager"
- repository = "https://charts.jetstack.io"
- chart = "cert-manager"
- version = "1.14.4"
-
- namespace = "cert-manager"
- create_namespace = true
-
- set {
- name = "installCRDs"
- value = true
- }
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/traefik/values.yaml b/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/traefik/values.yaml
deleted file mode 100644
index 2d5f64d0..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/traefik/values.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-# Custom values for Traefik
-# https://github.com/traefik/traefik-helm-chart/blob/master/traefik/values.yaml
-
-logs:
- general:
- level: "INFO"
- access:
- enabled: true
-
-providers:
- kubernetesIngress:
- enabled: true
- ingressClass: "traefik-cert-manager"
- kubernetesIngressRoute:
- enabled: true
diff --git a/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/traefik/variables.tf b/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/traefik/variables.tf
deleted file mode 100644
index 1d594641..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/cluster/modules/kubernetes/traefik/variables.tf
+++ /dev/null
@@ -1,17 +0,0 @@
-variable "letsencrypt_certificate_email" {
- description = "The email used to issue the Let's Encrypt certificate."
- type = string
- default = ""
-}
-
-variable "load_balancer_annotations" {
- description = "The Load Balancer service annotations."
- type = map(string)
- default = {}
-}
-
-variable "traefik_helm_chart_version" {
- description = "The helm chart Traefik version https://github.com/traefik/traefik-helm-chart/releases."
- type = string
- default = "26.0.0"
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/cluster/other-k8s/main.tf b/{{cookiecutter.project_dirname}}/terraform/cluster/other-k8s/main.tf
deleted file mode 100644
index 8db3563d..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/cluster/other-k8s/main.tf
+++ /dev/null
@@ -1,51 +0,0 @@
-terraform {
- required_providers {
- helm = {
- source = "hashicorp/helm"
- version = "~> 2.12"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- version = "~> 2.27"
- }
- }
-}
-
-/* Providers */
-
-provider "helm" {
- kubernetes {
- host = var.kubernetes_host
- token = var.kubernetes_token
- cluster_ca_certificate = base64decode(var.kubernetes_cluster_ca_certificate)
- }
-}
-
-provider "kubernetes" {
- host = var.kubernetes_host
- token = var.kubernetes_token
- cluster_ca_certificate = base64decode(var.kubernetes_cluster_ca_certificate)
-}
-
-/* Traefik */
-
-module "traefik" {
- source = "../modules/kubernetes/traefik"
-
- letsencrypt_certificate_email = var.letsencrypt_certificate_email
-}
-
-/* Reloader */
-
-resource "helm_release" "reloader" {
- name = "reloader"
- repository = "https://stakater.github.io/stakater-charts"
- chart = "reloader"
- version = "1.0.74"
-}
-
-/* Metrics */
-
-module "metrics" {
- source = "../modules/kubernetes/metrics"
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/cluster/other-k8s/variables.tf b/{{cookiecutter.project_dirname}}/terraform/cluster/other-k8s/variables.tf
deleted file mode 100644
index 8b0ae50f..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/cluster/other-k8s/variables.tf
+++ /dev/null
@@ -1,32 +0,0 @@
-variable "kubernetes_cluster_ca_certificate" {
- description = "The base64 encoded Kubernetes CA certificate."
- type = string
- sensitive = true
-}
-
-variable "kubernetes_host" {
- description = "The Kubernetes host."
- type = string
-}
-
-variable "kubernetes_token" {
- description = "A Kubernetes admin token."
- type = string
- sensitive = true
-}
-
-variable "letsencrypt_certificate_email" {
- description = "The email used to issue the Let's Encrypt certificate."
- type = string
- default = ""
-}
-
-variable "project_slug" {
- description = "The project slug."
- type = string
-}
-
-variable "stack_slug" {
- description = "The stack slug (e.g. 'main')."
- type = string
-}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/cluster/other-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/cluster/other-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}"
deleted file mode 100644
index 4ca44e9b..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/cluster/other-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}"
+++ /dev/null
@@ -1,4 +0,0 @@
-terraform {
- backend "http" {
- }
-}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/cluster/other-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/cluster/other-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}"
deleted file mode 100644
index 3849a361..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/cluster/other-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}"
+++ /dev/null
@@ -1,9 +0,0 @@
-terraform {
- cloud {
- organization = "{{ cookiecutter.terraform_cloud_organization }}"
-
- workspaces {
- tags = ["project:{{ cookiecutter.project_slug }}"]
- }
- }
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/cluster/vars/.tfvars b/{{cookiecutter.project_dirname}}/terraform/cluster/vars/.tfvars
deleted file mode 100644
index 620e1cbd..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/cluster/vars/.tfvars
+++ /dev/null
@@ -1,2 +0,0 @@
-{% if "cluster" in cookiecutter.tfvars %}{% for item in cookiecutter.tfvars.cluster|sort %}{{ item }}
-{% endfor %}{% endif %}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/cluster/vars/{% if \"cluster_dev\" in cookiecutter.tfvars %}dev.tfvars{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/cluster/vars/{% if \"cluster_dev\" in cookiecutter.tfvars %}dev.tfvars{% endif %}"
deleted file mode 100644
index 8655b27e..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/cluster/vars/{% if \"cluster_dev\" in cookiecutter.tfvars %}dev.tfvars{% endif %}"
+++ /dev/null
@@ -1,2 +0,0 @@
-{% for item in cookiecutter.tfvars.cluster_dev|sort %}{{ item }}
-{% endfor %}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/cluster/vars/{% if \"cluster_main\" in cookiecutter.tfvars %}main.tfvars{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/cluster/vars/{% if \"cluster_main\" in cookiecutter.tfvars %}main.tfvars{% endif %}"
deleted file mode 100644
index be5c1ab9..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/cluster/vars/{% if \"cluster_main\" in cookiecutter.tfvars %}main.tfvars{% endif %}"
+++ /dev/null
@@ -1,2 +0,0 @@
-{% for item in cookiecutter.tfvars.cluster_main|sort %}{{ item }}
-{% endfor %}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/cluster/vars/{% if \"cluster_stage\" in cookiecutter.tfvars %}stage.tfvars{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/cluster/vars/{% if \"cluster_stage\" in cookiecutter.tfvars %}stage.tfvars{% endif %}"
deleted file mode 100644
index 2fd980b5..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/cluster/vars/{% if \"cluster_stage\" in cookiecutter.tfvars %}stage.tfvars{% endif %}"
+++ /dev/null
@@ -1,2 +0,0 @@
-{% for item in cookiecutter.tfvars.cluster_stage|sort %}{{ item }}
-{% endfor %}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/digitalocean-k8s/main.tf b/{{cookiecutter.project_dirname}}/terraform/environment/digitalocean-k8s/main.tf
deleted file mode 100644
index a60ffe45..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/digitalocean-k8s/main.tf
+++ /dev/null
@@ -1,269 +0,0 @@
-locals {
- base_resource_name_prefix = var.stack_slug == "main" ? var.project_slug : "${var.project_slug}-${var.stack_slug}"
-
- namespace = kubernetes_namespace_v1.main.metadata[0].name
-
- domain_id = var.create_dns_records ? var.create_domain ? digitalocean_domain.main[0].id : data.digitalocean_domain.main[0].id : ""
-
- s3_host = var.digitalocean_spaces_bucket_available ? "${var.s3_region}.digitaloceanspaces.com" : var.s3_host
- s3_bucket_name = var.digitalocean_spaces_bucket_available ? "${local.base_resource_name_prefix}-s3-bucket" : var.s3_bucket_name
-
- postgres_dump_enabled = alltrue(
- [
- var.database_dumps_enabled,
- var.env_slug == "prod",
- var.s3_region != "",
- var.s3_access_id != "",
- var.s3_secret_key != "",
- local.s3_bucket_name != "",
- local.s3_host != "",
- ]
- )
-}
-
-terraform {
- required_providers {
- digitalocean = {
- source = "digitalocean/digitalocean"
- version = "~> 2.36"
- }
- helm = {
- source = "hashicorp/helm"
- version = "~> 2.12"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- version = "~> 2.27"
- }
- }
-}
-
-/* Providers */
-
-provider "digitalocean" {
- token = var.digitalocean_token
-
- spaces_access_id = var.s3_access_id
- spaces_secret_key = var.s3_secret_key
-}
-
-provider "helm" {
- kubernetes {
- host = data.digitalocean_kubernetes_cluster.main.endpoint
- token = data.digitalocean_kubernetes_cluster.main.kube_config[0].token
- cluster_ca_certificate = base64decode(
- data.digitalocean_kubernetes_cluster.main.kube_config[0].cluster_ca_certificate
- )
- }
-}
-
-provider "kubernetes" {
- host = data.digitalocean_kubernetes_cluster.main.endpoint
- token = data.digitalocean_kubernetes_cluster.main.kube_config[0].token
- cluster_ca_certificate = base64decode(
- data.digitalocean_kubernetes_cluster.main.kube_config[0].cluster_ca_certificate
- )
-}
-
-/* Data Sources */
-
-data "digitalocean_kubernetes_cluster" "main" {
- name = "${local.base_resource_name_prefix}-k8s-cluster"
-}
-
-data "digitalocean_database_cluster" "postgres" {
- name = "${local.base_resource_name_prefix}-database-cluster"
-}
-
-data "digitalocean_database_cluster" "redis" {
- count = var.use_redis ? 1 : 0
-
- name = "${local.base_resource_name_prefix}-redis-cluster"
-}
-
-data "digitalocean_spaces_bucket" "postgres_dump" {
- count = local.postgres_dump_enabled ? 1 : 0
-
- name = "${local.base_resource_name_prefix}-s3-bucket"
- region = var.s3_region
-}
-
-data "digitalocean_loadbalancer" "main" {
- name = "${local.base_resource_name_prefix}-load-balancer"
-}
-
-data "digitalocean_domain" "main" {
- count = var.create_dns_records && !var.create_domain ? 1 : 0
-
- name = var.project_domain
-}
-
-/* Database */
-
-resource "digitalocean_database_user" "postgres" {
- cluster_id = data.digitalocean_database_cluster.postgres.id
- name = "${var.project_slug}-${var.env_slug}-database-user"
-}
-
-resource "digitalocean_database_db" "postgres" {
- cluster_id = data.digitalocean_database_cluster.postgres.id
- name = "${var.project_slug}-${var.env_slug}-database"
-}
-
-resource "digitalocean_database_connection_pool" "postgres" {
- cluster_id = data.digitalocean_database_cluster.postgres.id
- db_name = digitalocean_database_db.postgres.name
- user = digitalocean_database_user.postgres.name
- name = "${var.project_slug}-${var.env_slug}-database-pool"
- mode = "transaction"
- size = var.database_connection_pool_size
-}
-
-/* Domain */
-
-resource "digitalocean_domain" "main" {
- count = var.create_dns_records && var.create_domain ? 1 : 0
-
- name = var.project_domain
-}
-
-/* DNS records */
-
-resource "digitalocean_record" "main" {
- for_each = toset(var.create_dns_records ? [for i in var.subdomains : i == "" ? "@" : i] : [])
-
- domain = local.domain_id
- type = "A"
- name = each.key
- value = data.digitalocean_loadbalancer.main.ip
-}
-
-resource "digitalocean_record" "monitoring" {
- count = var.create_dns_records && var.monitoring_subdomain != "" ? 1 : 0
-
- domain = local.domain_id
- type = "A"
- name = var.monitoring_subdomain
- value = data.digitalocean_loadbalancer.main.ip
-}
-
-/* Namespace */
-
-resource "kubernetes_namespace_v1" "main" {
- metadata {
- name = "${var.project_slug}-${var.env_slug}"
- }
-}
-
-/* Monitoring */
-
-module "monitoring" {
- count = var.monitoring_subdomain != "" ? 1 : 0
-
- source = "../modules/kubernetes/monitoring"
-
- grafana_password = var.grafana_password
- grafana_persistence_enabled = var.grafana_persistence_enabled
- grafana_user = var.grafana_user
- grafana_version = var.grafana_version
-
- s3_region = var.s3_region
- s3_access_id = var.s3_access_id
- s3_secret_key = var.s3_secret_key
- s3_bucket_name = local.s3_bucket_name
- s3_host = local.s3_host
-}
-
-/* Routing */
-
-module "routing" {
- source = "../modules/kubernetes/routing"
-
- env_slug = var.env_slug
- namespace = local.namespace
-
- project_domain = var.project_domain
- subdomains = var.subdomains
-
- basic_auth_enabled = var.basic_auth_enabled
- basic_auth_username = var.basic_auth_username
- basic_auth_password = var.basic_auth_password
-
- backend_service_extra_middlewares = var.backend_service_extra_traefik_middlewares
- backend_service_slug = var.backend_service_slug
- backend_service_paths = var.backend_service_paths
- backend_service_port = var.backend_service_port
-
- frontend_service_extra_middlewares = var.frontend_service_extra_traefik_middlewares
- frontend_service_slug = var.frontend_service_slug
- frontend_service_paths = var.frontend_service_paths
- frontend_service_port = var.frontend_service_port
-
- letsencrypt_certificate_email = var.letsencrypt_certificate_email
- letsencrypt_server = var.letsencrypt_server
- tls_certificate_crt = var.tls_certificate_crt
- tls_certificate_key = var.tls_certificate_key
-
- monitoring_subdomain = var.monitoring_subdomain
-
- secondary_domains = var.secondary_domains
-}
-
-/* Secrets */
-
-resource "kubernetes_secret_v1" "regcred" {
- metadata {
- name = "regcred"
- namespace = local.namespace
- }
- data = {
- ".dockerconfigjson" = jsonencode({
- auths = {
- "${var.registry_server}" = {
- auth = "${base64encode("${var.registry_username}:${var.registry_password}")}"
- }
- }
- })
- }
- type = "kubernetes.io/dockerconfigjson"
-}
-
-resource "kubernetes_secret_v1" "database_url" {
- metadata {
- name = "database-url"
- namespace = local.namespace
- }
- data = {
- DATABASE_URL = replace(digitalocean_database_connection_pool.postgres.private_uri, "/^postgresql:///", var.use_postgis ? "postgis://" : "postgresql://")
- }
-}
-
-resource "kubernetes_secret_v1" "redis_url" {
- count = var.use_redis ? 1 : 0
-
- metadata {
- name = "redis-url"
- namespace = local.namespace
- }
- data = {
- REDIS_URL = data.digitalocean_database_cluster.redis[0].private_uri
- }
-}
-
-/* Cron Jobs */
-
-module "database_dump_cronjob" {
- count = local.postgres_dump_enabled ? 1 : 0
-
- source = "../modules/kubernetes/database-dump-cronjob"
-
- namespace = local.namespace
-
- s3_region = var.s3_region
- s3_access_id = var.s3_access_id
- s3_secret_key = var.s3_secret_key
- s3_host = local.s3_host
- s3_bucket_name = local.s3_bucket_name
-
- database_url = digitalocean_database_connection_pool.postgres.private_uri
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/digitalocean-k8s/variables.tf b/{{cookiecutter.project_dirname}}/terraform/environment/digitalocean-k8s/variables.tf
deleted file mode 100644
index f7849a11..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/digitalocean-k8s/variables.tf
+++ /dev/null
@@ -1,257 +0,0 @@
-variable "backend_service_extra_traefik_middlewares" {
- description = "The backend service additional Traefik middlewares."
- type = list(string)
- default = []
-}
-
-variable "backend_service_paths" {
- description = "The backend service paths."
- type = list(string)
- default = []
-}
-
-variable "backend_service_port" {
- description = "The backend service port."
- type = number
- default = 8000
-}
-
-variable "backend_service_slug" {
- description = "The backend service slug."
- type = string
- default = ""
-}
-
-variable "basic_auth_enabled" {
- description = "The basic_auth switch."
- type = string
- default = ""
-}
-
-variable "basic_auth_password" {
- description = "The basic_auth password."
- type = string
- sensitive = true
- default = ""
-}
-
-variable "basic_auth_username" {
- description = "The basic_auth username."
- type = string
- default = ""
-}
-
-variable "create_dns_records" {
- description = "Tell if DigitalOcean DNS records should be created."
- type = bool
- default = true
-}
-
-variable "create_domain" {
- description = "Tell if a DigitalOcean domain should be created."
- type = bool
- default = false
-}
-
-variable "database_connection_pool_size" {
- description = "The DigitalOcean database connection pool size."
- type = number
- default = 1
-}
-
-variable "database_dumps_enabled" {
- description = "Enable database dumps."
- type = bool
- default = true
-}
-
-variable "digitalocean_spaces_bucket_available" {
- description = "Tell if a DigitalOcean Spaces bucket is available."
- type = bool
- default = false
-}
-
-variable "digitalocean_token" {
- description = "The DigitalOcean access token."
- type = string
- sensitive = true
-}
-
-variable "env_slug" {
- description = "The environment slug (e.g. 'prod')."
- type = string
-}
-
-variable "frontend_service_extra_traefik_middlewares" {
- description = "The frontend service additional Traefik middlewares."
- type = list(string)
- default = []
-}
-
-variable "frontend_service_paths" {
- description = "The frontend service paths."
- type = list(string)
- default = []
-}
-
-variable "frontend_service_port" {
- description = "The frontend service port."
- type = number
- default = 3000
-}
-
-variable "frontend_service_slug" {
- description = "The frontend service slug."
- type = string
- default = ""
-}
-
-variable "grafana_password" {
- description = "The Grafana admin password."
- type = string
- sensitive = true
- default = ""
-}
-
-variable "grafana_persistence_enabled" {
- description = "Enable grafana persistence."
- type = bool
- default = false
-}
-
-variable "grafana_user" {
- description = "The Grafana admin username."
- type = string
- default = "admin"
-}
-
-variable "grafana_version" {
- description = "The Grafana version."
- type = string
- default = "10.2.0"
-}
-
-variable "letsencrypt_certificate_email" {
- description = "The email used to issue the Let's Encrypt certificate."
- type = string
- default = ""
-}
-
-variable "letsencrypt_server" {
- description = "The Let's Encrypt server used to generate certificates."
- type = string
- default = ""
-}
-
-variable "monitoring_subdomain" {
- description = "The monitoring subdomain."
- type = string
- default = ""
-}
-
-variable "project_domain" {
- description = "The project domain."
- type = string
-}
-
-variable "project_slug" {
- description = "The project slug."
- type = string
-}
-
-variable "registry_password" {
- description = "The Docker image registry password."
- type = string
- sensitive = true
-}
-
-variable "registry_server" {
- description = "The Docker image registry server."
- type = string
-}
-
-variable "registry_username" {
- description = "The Docker image registry username."
- type = string
- sensitive = true
-}
-
-variable "s3_access_id" {
- description = "The S3 bucket access key ID."
- type = string
- default = ""
- sensitive = true
-}
-
-variable "s3_bucket_name" {
- description = "The S3 bucket name."
- type = string
- default = ""
-}
-
-variable "s3_host" {
- description = "The S3 bucket host."
- type = string
- default = ""
-}
-
-variable "s3_region" {
- description = "The S3 bucket region."
- type = string
- default = ""
-}
-
-variable "s3_secret_key" {
- description = "The S3 bucket secret access key."
- type = string
- default = ""
- sensitive = true
-}
-
-variable "secondary_domains" {
- description = "An optional list of secondary domains to redirect to the main one."
- type = list(string)
- default = []
-}
-
-variable "stack_slug" {
- description = "The stack slug (e.g. 'main')."
- type = string
-}
-
-variable "subdomains" {
- description = "The subdomains associated to the environment."
- type = list(string)
- default = []
-
- validation {
- condition = length(var.subdomains) > 0
- error_message = "At least one subdomain must be specified."
- }
-}
-
-variable "tls_certificate_crt" {
- description = "The base64-encoded PEM-formatted TLS full certificate."
- type = string
- sensitive = true
- default = ""
-}
-
-variable "tls_certificate_key" {
- description = "The base64-encoded PEM-formatted TLS private key."
- type = string
- sensitive = true
- default = ""
-}
-
-variable "use_postgis" {
- description = "Tell if the Postgres postgis extension is used."
- type = bool
- default = false
-}
-
-variable "use_redis" {
- description = "Tell if a Redis service is used."
- type = bool
- default = false
-}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/environment/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/environment/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}"
deleted file mode 100644
index 4ca44e9b..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/environment/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}"
+++ /dev/null
@@ -1,4 +0,0 @@
-terraform {
- backend "http" {
- }
-}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/environment/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/environment/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}"
deleted file mode 100644
index 3849a361..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/environment/digitalocean-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}"
+++ /dev/null
@@ -1,9 +0,0 @@
-terraform {
- cloud {
- organization = "{{ cookiecutter.terraform_cloud_organization }}"
-
- workspaces {
- tags = ["project:{{ cookiecutter.project_slug }}"]
- }
- }
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/database-dump-cronjob/main.tf b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/database-dump-cronjob/main.tf
deleted file mode 100644
index ae9e9a76..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/database-dump-cronjob/main.tf
+++ /dev/null
@@ -1,73 +0,0 @@
-terraform {
- required_providers {
- kubernetes = {
- source = "hashicorp/kubernetes"
- version = "~> 2.27"
- }
- }
-}
-
-resource "kubernetes_secret_v1" "main" {
- metadata {
- name = "postgres-dump"
- namespace = var.namespace
- }
-
- data = {
- AWS_ACCESS_KEY_ID = var.s3_access_id
- AWS_SECRET_ACCESS_KEY = var.s3_secret_key
- DATABASE_URL = var.database_url
- }
-}
-
-resource "kubernetes_config_map_v1" "main" {
- metadata {
- name = "postgres-dump"
- namespace = var.namespace
- }
-
- data = {
- AWS_S3_BACKUP_PATH = var.s3_backup_path
- AWS_S3_HOST = var.s3_host
- AWS_S3_REGION = var.s3_region
- AWS_STORAGE_BUCKET_NAME = var.s3_bucket_name
- }
-}
-
-resource "kubernetes_cron_job_v1" "main" {
- metadata {
- name = "postgresql-dump-cron"
- namespace = var.namespace
- }
-
- spec {
- schedule = "0 0 * * *"
- successful_jobs_history_limit = 31
- job_template {
- metadata {}
- spec {
- template {
- metadata {}
- spec {
- container {
- name = "postgresql-dump-to-s3"
- image = "20tab/postgres-dump-restore-to-from-s3:latest"
- command = ["/pg_dump_to_s3.sh"]
- env_from {
- config_map_ref {
- name = kubernetes_config_map_v1.main.metadata[0].name
- }
- }
- env_from {
- secret_ref {
- name = kubernetes_secret_v1.main.metadata[0].name
- }
- }
- }
- restart_policy = "OnFailure"
- }
- }
- }
- }
- }
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/database-dump-cronjob/variables.tf b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/database-dump-cronjob/variables.tf
deleted file mode 100644
index f95fc23b..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/database-dump-cronjob/variables.tf
+++ /dev/null
@@ -1,43 +0,0 @@
-variable "database_url" {
- description = "The database url."
- type = string
- sensitive = true
-}
-
-variable "namespace" {
- description = "The namespace for Kubernetes resources."
- type = string
-}
-
-variable "s3_access_id" {
- description = "The S3 bucket access key ID."
- type = string
- sensitive = true
-}
-
-variable "s3_backup_path" {
- description = "The S3 backup path."
- type = string
- default = "backup/postgres"
-}
-
-variable "s3_bucket_name" {
- description = "The S3 bucket name."
- type = string
-}
-
-variable "s3_host" {
- description = "The S3 bucket host."
- type = string
-}
-
-variable "s3_region" {
- description = "The S3 bucket region."
- type = string
-}
-
-variable "s3_secret_key" {
- description = "The S3 bucket secret access key."
- type = string
- sensitive = true
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/grafana/dashboards/k8s-logs.json b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/grafana/dashboards/k8s-logs.json
deleted file mode 100644
index ac81b79e..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/grafana/dashboards/k8s-logs.json
+++ /dev/null
@@ -1,290 +0,0 @@
-{
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": "-- Grafana --",
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "target": {
- "limit": 100,
- "matchAny": false,
- "tags": [],
- "type": "dashboard"
- },
- "type": "dashboard"
- }
- ]
- },
- "description": "Simple Loki dashboard",
- "editable": true,
- "fiscalYearStartMonth": 0,
- "gnetId": 13198,
- "graphTooltip": 0,
- "id": 2,
- "iteration": 1647877456540,
- "links": [],
- "liveNow": false,
- "panels": [
- {
- "datasource": {
- "type": "loki",
- "uid": "$datasource"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisLabel": "",
- "axisPlacement": "auto",
- "barAlignment": 0,
- "drawStyle": "line",
- "fillOpacity": 0,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "viz": false
- },
- "lineInterpolation": "linear",
- "lineWidth": 1,
- "pointSize": 5,
- "scaleDistribution": {
- "type": "linear"
- },
- "showPoints": "never",
- "spanNulls": true,
- "stacking": {
- "group": "A",
- "mode": "normal"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "short"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 24,
- "x": 0,
- "y": 0
- },
- "id": 4,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "table",
- "placement": "right"
- },
- "tooltip": {
- "mode": "multi",
- "sort": "desc"
- }
- },
- "pluginVersion": "9.1.3",
- "targets": [
- {
- "datasource": {
- "type": "loki",
- "uid": "P8E80F9AEF21F6940"
- },
- "expr": "count_over_time({namespace=~\"$namespace\", job=~\"$job\", pod=~\"$pod\", container=~\"$container\"} [1m])",
- "legendFormat": "{{ '{{ job }}' }}",
- "refId": "A"
- }
- ],
- "title": "Graph Time series",
- "type": "timeseries"
- },
- {
- "gridPos": {
- "h": 14,
- "w": 24,
- "x": 0,
- "y": 8
- },
- "id": 2,
- "options": {
- "dedupStrategy": "none",
- "enableLogDetails": true,
- "prettifyLogMessage": false,
- "showCommonLabels": false,
- "showLabels": false,
- "showTime": true,
- "sortOrder": "Descending",
- "wrapLogMessage": true
- },
- "pluginVersion": "7.1.3",
- "targets": [
- {
- "datasource": {
- "type": "loki",
- "uid": "P8E80F9AEF21F6940"
- },
- "expr": "{namespace=~\"$namespace\", job=~\"$job\", pod=~\"$pod\", container=~\"$container\"}",
- "legendFormat": "",
- "refId": "A"
- }
- ],
- "title": "Logs",
- "type": "logs"
- }
- ],
- "refresh": "1m",
- "schemaVersion": 35,
- "style": "dark",
- "tags": [],
- "templating": {
- "list": [
- {
- "current": {
- "selected": false,
- "text": "Loki",
- "value": "Loki"
- },
- "hide": 0,
- "includeAll": false,
- "multi": false,
- "name": "datasource",
- "options": [],
- "query": "loki",
- "queryValue": "",
- "refresh": 1,
- "regex": "",
- "skipUrlSync": false,
- "type": "datasource"
- },
- {
- "current": {
- "selected": true,
- "text": "All",
- "value": "$__all"
- },
- "definition": "label_values(namespace)",
- "hide": 0,
- "includeAll": true,
- "multi": false,
- "name": "namespace",
- "options": [],
- "query": "label_values(namespace)",
- "refresh": 1,
- "regex": "",
- "skipUrlSync": false,
- "sort": 0,
- "type": "query"
- },
- {
- "allValue": ".*",
- "current": {
- "selected": false,
- "text": "All",
- "value": "$__all"
- },
- "datasource": {
- "type": "loki",
- "uid": "$datasource"
- },
- "definition": "label_values(job)",
- "hide": 0,
- "includeAll": true,
- "label": "job",
- "multi": false,
- "name": "job",
- "options": [],
- "query": "label_values(job)",
- "refresh": 2,
- "regex": "",
- "skipUrlSync": false,
- "sort": 0,
- "tagValuesQuery": "",
- "tagsQuery": "",
- "type": "query",
- "useTags": false
- },
- {
- "current": {
- "selected": false,
- "text": "All",
- "value": "$__all"
- },
- "definition": "label_values(pod)",
- "hide": 0,
- "includeAll": true,
- "label": "pod",
- "multi": false,
- "name": "pod",
- "options": [],
- "query": "label_values(pod)",
- "refresh": 1,
- "regex": "",
- "skipUrlSync": false,
- "sort": 0,
- "type": "query"
- },
- {
- "current": {
- "selected": true,
- "text": "All",
- "value": "$__all"
- },
- "definition": "label_values(container)",
- "hide": 0,
- "includeAll": true,
- "label": "container",
- "multi": false,
- "name": "container",
- "options": [],
- "query": "label_values(container)",
- "refresh": 1,
- "regex": "",
- "skipUrlSync": false,
- "sort": 0,
- "type": "query"
- }
- ]
- },
- "time": {
- "from": "now-3h",
- "to": "now"
- },
- "timepicker": {
- "refresh_intervals": [
- "5s",
- "10s",
- "30s",
- "1m",
- "5m",
- "15m",
- "30m",
- "1h",
- "2h",
- "1d"
- ]
- },
- "timezone": "",
- "title": "Loki Logs",
- "uid": "ffxEJdvGz",
- "version": 1,
- "weekStart": ""
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/grafana/values.yaml b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/grafana/values.yaml
deleted file mode 100644
index ee369fa2..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/grafana/values.yaml
+++ /dev/null
@@ -1,37 +0,0 @@
-## Grafana Helm Chart
-## https://github.com/grafana/helm-charts/blob/main/charts/grafana/values.yaml
-
-## DO K8s Starter Kit
-## https://github.com/digitalocean/Kubernetes-Starter-Kit-Developers/blob/main/04-setup-observability/assets/manifests/prom-stack-values-v35.5.1.yaml
-
-persistence:
- storageClassName: do-block-storage
- accessModes: ["ReadWriteOnce"]
- size: 5Gi
-
-datasources:
- datasources.yaml:
- apiVersion: 1
- datasources:
- - name: Loki
- type: loki
- url: http://loki:3100
- access: proxy
- isDefault: true
-
-dashboardProviders:
- dashboardproviders.yaml:
- apiVersion: 1
- providers:
- - name: "default"
- orgId: 1
- folder: ""
- type: file
- disableDeletion: false
- editable: true
- options:
- path: /var/lib/grafana/dashboards/default
- foldersFromFilesStructure: true
-
-dashboardsConfigMaps:
- default: grafana-k8s-logs-dashboard
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/loki/pvc_storage.yaml b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/loki/pvc_storage.yaml
deleted file mode 100644
index 6d3b8a10..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/loki/pvc_storage.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
-loki:
- config:
- schema_config:
- configs:
- - from: "2020-10-24"
- store: boltdb-shipper
- object_store: filesystem
- schema: v11
- index:
- prefix: index_
- period: 24h
- storage_config:
- boltdb_shipper:
- cache_ttl: 24h # Can be increased for faster performance over longer query periods, uses more disk space
- shared_store: filesystem
-
- persistence:
- enabled: true
- storageClassName: do-block-storage
- accessModes: ["ReadWriteOnce"]
- size: 10Gi
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/loki/s3_storage.yaml b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/loki/s3_storage.yaml
deleted file mode 100644
index 0240b19b..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/loki/s3_storage.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-loki:
- config:
- schema_config:
- configs:
- - from: "2020-10-24"
- store: boltdb-shipper
- object_store: aws
- schema: v11
- index:
- prefix: index_
- period: 24h
- storage_config:
- boltdb_shipper:
- cache_ttl: 24h # Can be increased for faster performance over longer query periods, uses more disk space
- shared_store: aws
- aws:
- s3forcepathstyle: true
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/loki/values.yaml b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/loki/values.yaml
deleted file mode 100644
index 1c4b1512..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/loki/values.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-## Loki stack
-## https://github.com/digitalocean/Kubernetes-Starter-Kit-Developers/blob/main/04-setup-observability/assets/manifests/loki-stack-values-v2.6.4.yaml
-
-loki:
- enabled: true
- config:
- chunk_store_config:
- max_look_back_period: "4400h"
- table_manager:
- retention_deletes_enabled: true
- retention_period: "4400h"
- limits_config:
- max_query_length: "2200h"
-
-promtail:
- enabled: true
-
-fluent-bit:
- enabled: false
-
-grafana:
- enabled: false
-
-prometheus:
- enabled: false
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/main.tf b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/main.tf
deleted file mode 100644
index 29d9604d..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/main.tf
+++ /dev/null
@@ -1,106 +0,0 @@
-locals {
- namespace = kubernetes_namespace_v1.log_storage.metadata[0].name
-
- s3_storage_enabled = alltrue(
- [
- var.s3_access_id != "",
- var.s3_bucket_name != "",
- var.s3_host != "",
- var.s3_region != "",
- var.s3_secret_key != "",
- ]
- )
-
-}
-
-terraform {
- required_providers {
- helm = {
- source = "hashicorp/helm"
- version = "~> 2.12"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- version = "~> 2.27"
- }
- }
-}
-
-/* Grafana Loki - logs storage */
-
-resource "kubernetes_namespace_v1" "log_storage" {
- metadata {
- name = "log-storage"
- }
-}
-
-resource "helm_release" "loki" {
- name = "loki"
- repository = "https://grafana.github.io/helm-charts"
- chart = "loki-stack"
- version = "2.10.2"
-
- namespace = local.namespace
-
- values = [
- file("${path.module}/loki/values.yaml"),
- local.s3_storage_enabled ? file("${path.module}/loki/s3_storage.yaml") : file("${path.module}/loki/pvc_storage.yaml")
- ]
-
- dynamic "set" {
- for_each = local.s3_storage_enabled ? {
- "loki.config.storage_config.aws.access_key_id" = var.s3_access_id
- "loki.config.storage_config.aws.bucketnames" = var.s3_bucket_name
- "loki.config.storage_config.aws.endpoint" = var.s3_host
- "loki.config.storage_config.aws.region" = var.s3_region
- "loki.config.storage_config.aws.secret_access_key" = var.s3_secret_key
- } : {}
-
- content {
- name = set.key
- value = set.value
- }
- }
-
-}
-
-/* Grafana */
-
-resource "kubernetes_config_map_v1" "k8s_logs_dashboard" {
-
- metadata {
- name = "grafana-k8s-logs-dashboard"
- namespace = local.namespace
- }
-
- data = {
- "k8s-logs.json" = file("${path.module}/grafana/dashboards/k8s-logs.json")
- }
-}
-
-resource "helm_release" "grafana" {
- name = "grafana"
- repository = "https://grafana.github.io/helm-charts"
- chart = "grafana"
- version = "7.3.7"
-
- namespace = local.namespace
-
- values = [file("${path.module}/grafana/values.yaml")]
-
- dynamic "set" {
- for_each = {
- "image.tag" = var.grafana_version
- "adminUser" = var.grafana_user
- "adminPassword" = var.grafana_password
- "persistence.enabled" = var.grafana_persistence_enabled
- }
-
- content {
- name = set.key
- value = set.value
- }
- }
-
- depends_on = [kubernetes_config_map_v1.k8s_logs_dashboard]
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/variables.tf b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/variables.tf
deleted file mode 100644
index 00d035ab..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/monitoring/variables.tf
+++ /dev/null
@@ -1,53 +0,0 @@
-variable "grafana_password" {
- description = "The Grafana admin password."
- type = string
- sensitive = true
-}
-
-variable "grafana_persistence_enabled" {
- description = "Enable grafana persistence."
- type = bool
- default = false
-}
-
-variable "grafana_user" {
- description = "The Grafana admin username."
- type = string
-}
-
-variable "grafana_version" {
- description = "The Grafana version."
- type = string
-}
-
-variable "s3_access_id" {
- description = "The S3 bucket access key ID."
- type = string
- default = ""
- sensitive = true
-}
-
-variable "s3_bucket_name" {
- description = "The S3 bucket name."
- type = string
- default = ""
-}
-
-variable "s3_host" {
- description = "The S3 host."
- type = string
- default = ""
-}
-
-variable "s3_region" {
- description = "The S3 bucket region."
- type = string
- default = ""
-}
-
-variable "s3_secret_key" {
- description = "The S3 bucket secret access key."
- type = string
- default = ""
- sensitive = true
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/postgres/main.tf b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/postgres/main.tf
deleted file mode 100644
index ff43f890..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/postgres/main.tf
+++ /dev/null
@@ -1,152 +0,0 @@
-terraform {
- required_providers {
- kubernetes = {
- source = "hashicorp/kubernetes"
- version = "~> 2.27"
- }
- random = {
- source = "hashicorp/random"
- version = "~> 3.6"
- }
- }
-}
-
-/* Volumes */
-
-resource "kubernetes_persistent_volume_v1" "main" {
- metadata {
- name = "${var.namespace}-postgres"
- }
- spec {
- capacity = {
- storage = var.persistent_volume_capacity
- }
- access_modes = ["ReadWriteOnce"]
- persistent_volume_source {
- host_path {
- path = var.persistent_volume_host_path
- }
- }
- }
-}
-
-resource "kubernetes_persistent_volume_claim_v1" "main" {
- metadata {
- name = "postgres"
- namespace = var.namespace
- }
- spec {
- access_modes = ["ReadWriteOnce"]
- resources {
- requests = {
- storage = var.persistent_volume_claim_capacity
- }
- }
- volume_name = kubernetes_persistent_volume_v1.main.metadata[0].name
- }
-}
-
-/* Configuration */
-
-resource "random_password" "main" {
- length = 50
- special = false
-}
-
-resource "kubernetes_secret_v1" "main" {
- metadata {
- name = "postgres"
- namespace = var.namespace
- }
- data = {
- POSTGRES_USER = var.database_user
- POSTGRES_PASSWORD = random_password.main.result
- }
-}
-
-resource "kubernetes_config_map_v1" "main" {
- metadata {
- name = "postgres"
- namespace = var.namespace
- }
- data = {
- POSTGRES_DB = var.database_name
- }
-}
-
-/* Deployment */
-
-resource "kubernetes_deployment_v1" "main" {
- metadata {
- name = "postgres"
- namespace = var.namespace
- annotations = {
- "reloader.stakater.com/auto" = "true"
- }
- }
- spec {
- replicas = 1
- selector {
- match_labels = {
- component = "postgres"
- }
- }
- template {
- metadata {
- labels = {
- component = "postgres"
- }
- }
- spec {
- volume {
- name = "postgres"
- persistent_volume_claim {
- claim_name = kubernetes_persistent_volume_claim_v1.main.metadata[0].name
- }
- }
- container {
- name = "postgres"
- image = var.postgres_image
- port {
- container_port = 5432
- }
- volume_mount {
- name = "postgres"
- mount_path = "/var/lib/postgresql/data"
- sub_path = "postgres"
- }
- env_from {
- config_map_ref {
- name = kubernetes_config_map_v1.main.metadata[0].name
- }
- }
- env_from {
- secret_ref {
- name = kubernetes_secret_v1.main.metadata[0].name
- }
- }
- }
- }
- }
- }
-}
-
-/* Cluster IP Service */
-
-resource "kubernetes_service_v1" "main" {
- metadata {
- name = "postgres"
- namespace = var.namespace
- }
- spec {
- type = "ClusterIP"
- selector = {
- component = "postgres"
- }
- port {
- port = 5432
- target_port = 5432
- }
- }
- depends_on = [kubernetes_deployment_v1.main]
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/postgres/outputs.tf b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/postgres/outputs.tf
deleted file mode 100644
index c3d9177b..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/postgres/outputs.tf
+++ /dev/null
@@ -1,5 +0,0 @@
-output "database_url" {
- description = "The Postgres database url."
- value = "postgres://${var.database_user}:${random_password.main.result}@${kubernetes_service_v1.main.metadata[0].name}:5432/${var.database_name}"
- sensitive = true
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/postgres/variables.tf b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/postgres/variables.tf
deleted file mode 100644
index 63f7a426..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/postgres/variables.tf
+++ /dev/null
@@ -1,37 +0,0 @@
-variable "database_name" {
- description = "The Postgres database name."
- type = string
-}
-
-variable "database_user" {
- description = "The Postgres database user."
- type = string
-}
-
-variable "namespace" {
- description = "The namespace for Kubernetes resources."
- type = string
-}
-
-variable "persistent_volume_capacity" {
- description = "The persistent volume capacity (e.g. 1Gi)."
- type = string
- default = "10Gi"
-}
-
-variable "persistent_volume_claim_capacity" {
- description = "The persistent volume claim capacity (e.g. 1Gi)."
- type = string
- default = "10Gi"
-}
-
-variable "persistent_volume_host_path" {
- description = "The persistent volume host path."
- type = string
-}
-
-variable "postgres_image" {
- description = "The Postgres Docker image."
- type = string
- default = "postgres:14"
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/redis/main.tf b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/redis/main.tf
deleted file mode 100644
index 4d8fae08..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/redis/main.tf
+++ /dev/null
@@ -1,99 +0,0 @@
-terraform {
- required_providers {
- kubernetes = {
- source = "hashicorp/kubernetes"
- version = "~> 2.27"
- }
- random = {
- source = "hashicorp/random"
- version = "~> 3.6"
- }
- }
-}
-
-/* Configurationn */
-
-resource "random_password" "main" {
- length = 50
- special = false
-}
-
-resource "kubernetes_config_map_v1" "main" {
- metadata {
- name = "redis"
- namespace = var.namespace
- }
-
- data = {
- "redis.conf" = "requirepass \"${random_password.main.result}\"\n"
- }
-}
-
-
-/* Deployment */
-
-resource "kubernetes_deployment_v1" "main" {
- metadata {
- name = "redis"
- namespace = var.namespace
- annotations = {
- "reloader.stakater.com/auto" = "true"
- }
- }
- spec {
- replicas = 1
- selector {
- match_labels = {
- component = "redis"
- }
- }
- template {
- metadata {
- labels = {
- component = "redis"
- }
- }
- spec {
- volume {
- name = "redis"
- config_map {
- name = kubernetes_config_map_v1.main.metadata[0].name
- }
- }
- container {
- name = "redis"
- image = var.redis_image
- command = ["redis-server", "/etc/redis/redis.conf"]
- port {
- container_port = 6379
- }
- volume_mount {
- name = "redis"
- mount_path = "/etc/redis"
- }
- }
- }
- }
- }
-}
-
-/* Cluster IP Service */
-
-resource "kubernetes_service_v1" "main" {
-
- metadata {
- name = "redis"
- namespace = var.namespace
- }
- spec {
- type = "ClusterIP"
- selector = {
- component = "redis"
- }
- port {
- port = 6379
- target_port = 6379
- }
-
- }
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/redis/outputs.tf b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/redis/outputs.tf
deleted file mode 100644
index 0e950b03..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/redis/outputs.tf
+++ /dev/null
@@ -1,5 +0,0 @@
-output "redis_url" {
- description = "The Redis server URL."
- value = "redis://:${random_password.main.result}@${kubernetes_service_v1.main.metadata[0].name}:6379"
- sensitive = true
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/redis/variables.tf b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/redis/variables.tf
deleted file mode 100644
index bf466b4a..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/redis/variables.tf
+++ /dev/null
@@ -1,10 +0,0 @@
-variable "namespace" {
- description = "The namespace for Kubernetes resources."
- type = string
-}
-
-variable "redis_image" {
- description = "The Redis Docker image."
- type = string
- default = "redis:6"
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/routing/main.tf b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/routing/main.tf
deleted file mode 100644
index ba244d22..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/routing/main.tf
+++ /dev/null
@@ -1,446 +0,0 @@
-locals {
- basic_auth_ready = alltrue(
- [
- var.basic_auth_username != "",
- var.basic_auth_password != ""
- ]
- )
-
- domains = [for i in var.subdomains : i == "" ? var.project_domain : "${i}.${var.project_domain}"]
- monitoring_domain = var.monitoring_subdomain != "" ? "${var.monitoring_subdomain}.${var.project_domain}" : ""
-
- traefik_hosts = join(", ", [for i in local.domains : "`${i}`"])
-
- secondary_domains_traefik_hosts = join(", ", [for i in var.secondary_domains : "`${i}`"])
-
- http_redirect_traefik_hosts = join(", ", [for i in concat(
- local.domains,
- var.secondary_domains,
- local.monitoring_domain != "" ? [local.monitoring_domain] : [],
- ) : "`${i}`"])
-
- base_middlewares = var.basic_auth_enabled && local.basic_auth_ready ? [{ "name" : "traefik-basic-auth" }] : []
-
- letsencrypt_enabled = var.letsencrypt_certificate_email != ""
- manual_certificate_enabled = var.tls_certificate_crt != "" && var.tls_certificate_key != ""
- tls_enabled = local.manual_certificate_enabled || local.letsencrypt_enabled
-
- tls_secret_name = local.tls_enabled ? "tls-certificate" : ""
-}
-
-terraform {
- required_providers {
- kubernetes = {
- source = "hashicorp/kubernetes"
- version = "~> 2.27"
- }
- }
-}
-
-/* Basic Auth */
-
-resource "kubernetes_secret_v1" "traefik_basic_auth" {
- count = var.basic_auth_enabled && local.basic_auth_ready ? 1 : 0
-
- metadata {
- name = "basic-auth"
- namespace = var.namespace
- }
-
- data = {
- username = var.basic_auth_username
- password = var.basic_auth_password
- }
-
- type = "kubernetes.io/basic-auth"
-}
-
-resource "kubernetes_manifest" "traefik_basic_auth_middleware" {
- count = var.basic_auth_enabled && local.basic_auth_ready ? 1 : 0
-
- manifest = {
- apiVersion = "traefik.io/v1alpha1"
- kind = "Middleware"
- metadata = {
- name = "traefik-basic-auth"
- namespace = var.namespace
- }
- spec = {
- basicAuth = {
- removeHeader = true
- secret = kubernetes_secret_v1.traefik_basic_auth[0].metadata[0].name
- }
- }
- }
-}
-
-/* TLS Secret */
-
-resource "kubernetes_secret_v1" "tls" {
- count = local.manual_certificate_enabled ? 1 : 0
-
- metadata {
- name = local.tls_secret_name
- namespace = var.namespace
- }
-
- data = {
- "tls.crt" = base64decode(var.tls_certificate_crt)
- "tls.key" = base64decode(var.tls_certificate_key)
- }
-
- type = "kubernetes.io/tls"
-}
-
-/* Let's Encrypt Certificate */
-
-resource "kubernetes_manifest" "issuer" {
- count = local.letsencrypt_enabled ? 1 : 0
-
- manifest = {
- apiVersion = "cert-manager.io/v1"
- kind = "Issuer"
- metadata = {
- name = "letsencrypt"
- namespace = var.namespace
- }
- spec = {
- acme = {
- email = var.letsencrypt_certificate_email
- server = coalesce(var.letsencrypt_server, "https://acme-v02.api.letsencrypt.org/directory")
- privateKeySecretRef = {
- name = "issuer-private-key"
- }
- solvers = [
- {
- http01 = {
- ingress = {
- class = "traefik-cert-manager"
- }
- }
- }
- ]
- }
- }
- }
-}
-
-resource "kubernetes_manifest" "certificate" {
- count = local.letsencrypt_enabled ? 1 : 0
-
- manifest = {
- apiVersion = "cert-manager.io/v1"
- kind = "Certificate"
- metadata = {
- name = "letsencrypt"
- namespace = var.namespace
- }
- spec = {
- secretName = local.tls_secret_name
- issuerRef = {
- name = "letsencrypt"
- kind = "Issuer"
- }
- dnsNames = concat(
- local.domains, var.secondary_domains, local.monitoring_domain != "" ? [local.monitoring_domain] : []
- )
- }
- }
-}
-
-/* Main Ingress Route */
-
-resource "kubernetes_manifest" "main_ingress_route" {
- manifest = {
- apiVersion = "traefik.io/v1alpha1"
- kind = "IngressRoute"
- metadata = {
- name = "main"
- namespace = var.namespace
- }
- spec = merge(
- {
- entryPoints = local.tls_enabled ? ["websecure"] : ["web"]
- routes = concat(
- # frontend routes
- [
- for path in toset(var.frontend_service_paths) : {
- kind = "Rule"
- match = "Host(${local.traefik_hosts}) && PathPrefix(`${path}`)"
- middlewares = concat(
- local.base_middlewares,
- [for i in var.frontend_service_extra_middlewares : { name = i }],
- )
- services = [
- {
- name = var.frontend_service_slug
- port = var.frontend_service_port
- }
- ]
- }
- ],
- # backend routes
- [
- for path in toset(var.backend_service_paths) : {
- kind = "Rule"
- match = "Host(${local.traefik_hosts}) && PathPrefix(`${path}`)"
- middlewares = concat(
- local.base_middlewares,
- [for i in var.backend_service_extra_middlewares : { name = i }],
- )
- services = [
- {
- name = var.backend_service_slug
- port = var.backend_service_port
- }
- ]
- }
- ],
- )
- },
- local.tls_enabled ? {
- tls = {
- secretName = local.tls_secret_name
- }
- } : {}
- )
- }
-}
-
-/* Monitoring Ingress Route */
-
-resource "kubernetes_manifest" "monitoring_ingress_route" {
- count = local.monitoring_domain != "" ? 1 : 0
-
- manifest = {
- apiVersion = "traefik.io/v1alpha1"
- kind = "IngressRoute"
- metadata = {
- name = "monitoring"
- namespace = "log-storage"
- }
- spec = merge(
- {
- entryPoints = local.tls_enabled ? ["websecure"] : ["web"]
- routes = [
- {
- kind = "Rule"
- match = "Host(`${local.monitoring_domain}`)"
- services = [
- {
- name = "grafana"
- port = 80
- }
- ]
- }
- ]
- },
- local.tls_enabled ? {
- tls = {
- secretName = local.tls_secret_name
- }
- } : {}
- )
- }
-}
-
-/* Metrics Ingress Route */
-
-resource "kubernetes_secret_v1" "metrics_basic_auth" {
- count = local.basic_auth_ready ? 1 : 0
-
- metadata {
- name = "metrics-basic-auth-${var.env_slug}"
- namespace = "kube-system"
- }
-
- data = {
- username = var.basic_auth_username
- password = var.basic_auth_password
- }
-
- type = "kubernetes.io/basic-auth"
-}
-
-resource "kubernetes_manifest" "metrics_basic_auth_middleware" {
- count = local.basic_auth_ready ? 1 : 0
-
- manifest = {
- apiVersion = "traefik.io/v1alpha1"
- kind = "Middleware"
- metadata = {
- name = "metrics-basic-auth-${var.env_slug}"
- namespace = "kube-system"
- }
- spec = {
- basicAuth = {
- removeHeader = true
- secret = kubernetes_secret_v1.metrics_basic_auth[0].metadata[0].name
- }
- }
- }
-}
-
-resource "kubernetes_manifest" "metrics_ingress_route" {
-
- manifest = {
- apiVersion = "traefik.io/v1alpha1"
- kind = "IngressRoute"
- metadata = {
- name = "metrics-${var.env_slug}"
- namespace = "kube-system"
- }
- spec = merge(
- {
- entryPoints = local.tls_secret_name != "" ? ["websecure"] : ["web"]
- routes = concat(
- local.basic_auth_ready ? [
- {
- kind = "Rule"
- match = "Host(${local.traefik_hosts}) && PathPrefix(`/metrics`)"
- middlewares = [{ "name" : "metrics-basic-auth-${var.env_slug}" }]
- services = [
- {
- name = "kube-state-metrics"
- port = 8080
- }
- ]
- }] : [],
- [{
- kind = "Rule"
- match = "Host(${local.traefik_hosts}) && PathPrefix(`/healthz`)"
- middlewares = []
- services = [
- {
- name = "kube-state-metrics"
- port = 8080
- }
- ]
- }
- ])
- },
- local.tls_secret_name != "" ? {
- tls = {
- secretName = local.tls_secret_name
- }
- } : {}
- )
- }
-}
-
-/* HTTPS Redirect */
-
-resource "kubernetes_manifest" "middleware_redirect_to_https" {
- count = local.tls_enabled ? 1 : 0
-
- manifest = {
- apiVersion = "traefik.io/v1alpha1"
- kind = "Middleware"
- metadata = {
- name = "redirect-to-https"
- namespace = var.namespace
- }
- spec = {
- redirectScheme = {
- scheme = "https"
- permanent = true
- }
- }
- }
-}
-
-resource "kubernetes_manifest" "ingressroute_redirect_to_https" {
- count = local.tls_enabled ? 1 : 0
-
- manifest = {
- apiVersion = "traefik.io/v1alpha1"
- kind = "IngressRoute"
- metadata = {
- name = "redirect-to-https"
- namespace = var.namespace
- }
- spec = merge(
- {
- entryPoints = ["web"]
- routes = [
- {
- kind = "Rule"
- match = "Host(${local.http_redirect_traefik_hosts})"
- middlewares = [
- { name = "redirect-to-https" }
- ]
- services = [
- {
- name = coalesce(var.frontend_service_slug, var.backend_service_slug)
- port = coalesce(var.frontend_service_port, var.backend_service_port)
- }
- ]
- }
- ]
- }
- )
- }
-}
-
-/* Secondary Domains Redirect */
-
-resource "kubernetes_manifest" "middleware_secondary_domains_redirect" {
- manifest = {
- apiVersion = "traefik.io/v1alpha1"
- kind = "Middleware"
- metadata = {
- name = "redirect-secondary-domains"
- namespace = var.namespace
- }
- spec = {
- redirectRegex = {
- regex = join(
- "", [
- "^(https?)://(?:",
- join("|", [for i in var.secondary_domains : replace(i, ".", "\\.")]),
- ")(.*)$"
- ]
- )
- replacement = "$1://${local.domains[0]}$2"
- }
- }
- }
-
- computed_fields = ["metadata.labels.domain", "metadata.name"]
-}
-
-resource "kubernetes_manifest" "ingressroute_secondary_domains_redirect" {
- manifest = {
- apiVersion = "traefik.io/v1alpha1"
- kind = "IngressRoute"
- metadata = {
- name = "redirect-secondary-domains"
- namespace = var.namespace
- }
- spec = merge(
- {
- entryPoints = local.tls_enabled ? ["websecure"] : ["web"]
- routes = [
- {
- kind = "Rule"
- match = "Host(${local.secondary_domains_traefik_hosts})"
- middlewares = [
- { name = "redirect-secondary-domains" },
- ]
- services = [
- {
- name = coalesce(var.frontend_service_slug, var.backend_service_slug)
- port = coalesce(var.frontend_service_port, var.backend_service_port)
- }
- ]
- }
- ]
- },
- local.letsencrypt_enabled ? {
- tls = {
- secretName = "tls-letsencrypt"
- }
- } : {}
- )
- }
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/routing/outputs.tf b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/routing/outputs.tf
deleted file mode 100644
index 151b2e30..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/routing/outputs.tf
+++ /dev/null
@@ -1,4 +0,0 @@
-output "tls_secret_name" {
- description = "The name of the TLS certificate Kubernetes secret."
- value = local.tls_secret_name
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/routing/variables.tf b/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/routing/variables.tf
deleted file mode 100644
index 4c5a2969..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/modules/kubernetes/routing/variables.tf
+++ /dev/null
@@ -1,130 +0,0 @@
-variable "backend_service_extra_middlewares" {
- description = "The backend service additional middlewares."
- type = list(string)
- default = []
-}
-
-variable "backend_service_paths" {
- description = "The backend service paths."
- type = list(string)
- default = []
-}
-
-variable "backend_service_port" {
- description = "The backend service port."
- type = number
- default = 8000
-}
-
-variable "backend_service_slug" {
- description = "The backend service slug."
- type = string
- default = ""
-}
-
-variable "basic_auth_enabled" {
- description = "Tell if the basic auth should be enabled."
- type = bool
- default = false
-}
-
-variable "basic_auth_password" {
- description = "The basic_auth password."
- type = string
- sensitive = true
- default = ""
-}
-
-variable "basic_auth_username" {
- description = "The basic_auth username."
- type = string
- default = ""
-}
-
-variable "env_slug" {
- description = "The environment slug (e.g. 'prod')."
- type = string
-}
-
-variable "frontend_service_extra_middlewares" {
- description = "The frontend service additional middlewares."
- type = list(string)
- default = []
-}
-
-variable "frontend_service_paths" {
- description = "The frontend service paths."
- type = list(string)
- default = []
-}
-
-variable "frontend_service_port" {
- description = "The frontend service port."
- type = number
- default = 3000
-}
-
-variable "frontend_service_slug" {
- description = "The frontend service slug."
- type = string
- default = ""
-}
-
-variable "letsencrypt_certificate_email" {
- description = "The email used to issue the Let's Encrypt certificate."
- type = string
- default = ""
-}
-
-variable "letsencrypt_server" {
- description = "The Let's Encrypt server used to generate certificates."
- type = string
- default = ""
-}
-
-variable "monitoring_subdomain" {
- description = "The monitoring subdomain, if enabled."
- type = string
- default = ""
-}
-
-variable "namespace" {
- description = "The namespace for Kubernetes resources."
- type = string
-}
-
-variable "project_domain" {
- description = "The project domain."
- type = string
-}
-
-variable "secondary_domains" {
- description = "An optional list of secondary domains to redirect to the main one."
- type = list(string)
- default = []
-}
-
-variable "subdomains" {
- description = "The subdomains associated to the environment."
- type = list(string)
- default = []
-
- validation {
- condition = length(var.subdomains) > 0
- error_message = "At least one subdomain must be specified."
- }
-}
-
-variable "tls_certificate_crt" {
- description = "The base64-encoded PEM-formatted TLS full certificate."
- type = string
- sensitive = true
- default = ""
-}
-
-variable "tls_certificate_key" {
- description = "The base64-encoded PEM-formatted TLS private key."
- type = string
- sensitive = true
- default = ""
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/other-k8s/main.tf b/{{cookiecutter.project_dirname}}/terraform/environment/other-k8s/main.tf
deleted file mode 100644
index 7adcfc20..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/other-k8s/main.tf
+++ /dev/null
@@ -1,195 +0,0 @@
-locals {
- namespace = kubernetes_namespace_v1.main.metadata[0].name
-
- postgres_dump_enabled = alltrue(
- [
- var.database_dumps_enabled,
- var.env_slug == "prod",
- var.s3_region != "",
- var.s3_access_id != "",
- var.s3_secret_key != "",
- var.s3_bucket_name != "",
- ]
- )
-}
-
-terraform {
- required_providers {
- kubernetes = {
- source = "hashicorp/kubernetes"
- version = "~> 2.27"
- }
- helm = {
- source = "hashicorp/helm"
- version = "~> 2.12"
- }
- random = {
- source = "hashicorp/random"
- version = "~> 3.6"
- }
- }
-}
-
-/* Providers */
-
-provider "kubernetes" {
- host = var.kubernetes_host
- token = var.kubernetes_token
- cluster_ca_certificate = base64decode(var.kubernetes_cluster_ca_certificate)
-}
-
-provider "helm" {
- kubernetes {
- host = var.kubernetes_host
- token = var.kubernetes_token
- cluster_ca_certificate = base64decode(var.kubernetes_cluster_ca_certificate)
- }
-}
-
-/* Namespace */
-
-resource "kubernetes_namespace_v1" "main" {
- metadata {
- name = "${var.project_slug}-${var.env_slug}"
- }
-}
-
-/* Databases */
-
-module "postgres" {
- source = "../modules/kubernetes/postgres"
-
- namespace = local.namespace
-
- postgres_image = var.postgres_image
-
- database_name = "${var.project_slug}-${var.env_slug}-database"
- database_user = "${var.project_slug}-${var.env_slug}-database-user"
-
- persistent_volume_capacity = var.postgres_persistent_volume_capacity
- persistent_volume_claim_capacity = var.postgres_persistent_volume_claim_capacity
- persistent_volume_host_path = var.postgres_persistent_volume_host_path
-}
-
-module "redis" {
- count = var.use_redis ? 1 : 0
-
- source = "../modules/kubernetes/redis"
-
- namespace = local.namespace
-
- redis_image = var.redis_image
-}
-
-/* Monitoring */
-
-module "monitoring" {
- count = var.monitoring_subdomain != "" ? 1 : 0
-
- source = "../modules/kubernetes/monitoring"
-
- grafana_password = var.grafana_password
- grafana_persistence_enabled = var.grafana_persistence_enabled
- grafana_user = var.grafana_user
- grafana_version = var.grafana_version
-
- s3_region = var.s3_region
- s3_access_id = var.s3_access_id
- s3_secret_key = var.s3_secret_key
- s3_bucket_name = var.s3_bucket_name
- s3_host = var.s3_host
-}
-
-/* Routing */
-
-module "routing" {
- source = "../modules/kubernetes/routing"
-
- env_slug = var.env_slug
- namespace = local.namespace
-
- project_domain = var.project_domain
- subdomains = var.subdomains
-
- basic_auth_enabled = var.basic_auth_enabled
- basic_auth_username = var.basic_auth_username
- basic_auth_password = var.basic_auth_password
-
- backend_service_extra_middlewares = var.backend_service_extra_traefik_middlewares
- backend_service_slug = var.backend_service_slug
- backend_service_paths = var.backend_service_paths
- backend_service_port = var.backend_service_port
-
- frontend_service_extra_middlewares = var.frontend_service_extra_traefik_middlewares
- frontend_service_slug = var.frontend_service_slug
- frontend_service_paths = var.frontend_service_paths
- frontend_service_port = var.frontend_service_port
-
- letsencrypt_certificate_email = var.letsencrypt_certificate_email
- letsencrypt_server = var.letsencrypt_server
- tls_certificate_crt = var.tls_certificate_crt
- tls_certificate_key = var.tls_certificate_key
-
- monitoring_subdomain = var.monitoring_subdomain
-
- secondary_domains = var.secondary_domains
-}
-
-/* Secrets */
-
-resource "kubernetes_secret_v1" "regcred" {
- metadata {
- name = "regcred"
- namespace = local.namespace
- }
- data = {
- ".dockerconfigjson" = jsonencode({
- auths = {
- "${var.registry_server}" = {
- auth = "${base64encode("${var.registry_username}:${var.registry_password}")}"
- }
- }
- })
- }
- type = "kubernetes.io/dockerconfigjson"
-}
-
-resource "kubernetes_secret_v1" "database_url" {
- metadata {
- name = "database-url"
- namespace = local.namespace
- }
- data = {
- DATABASE_URL = module.postgres.database_url
- }
-}
-
-resource "kubernetes_secret_v1" "redis_url" {
- count = var.use_redis ? 1 : 0
-
- metadata {
- name = "redis-url"
- namespace = local.namespace
- }
- data = {
- REDIS_URL = module.redis[0].redis_url
- }
-}
-
-/* Cron Jobs */
-
-module "database_dump_cronjob" {
- count = local.postgres_dump_enabled ? 1 : 0
-
- source = "../modules/kubernetes/database-dump-cronjob"
-
- namespace = local.namespace
-
- s3_region = var.s3_region
- s3_access_id = var.s3_access_id
- s3_secret_key = var.s3_secret_key
- s3_host = var.s3_host
- s3_bucket_name = var.s3_bucket_name
-
- database_url = module.postgres.database_url
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/other-k8s/variables.tf b/{{cookiecutter.project_dirname}}/terraform/environment/other-k8s/variables.tf
deleted file mode 100644
index 852a956f..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/other-k8s/variables.tf
+++ /dev/null
@@ -1,268 +0,0 @@
-variable "backend_service_extra_traefik_middlewares" {
- description = "The backend service additional Traefik middlewares."
- type = list(string)
- default = []
-}
-
-variable "backend_service_paths" {
- description = "The backend service paths."
- type = list(string)
- default = []
-}
-
-variable "backend_service_port" {
- description = "The backend service port."
- type = number
- default = 8000
-}
-
-variable "backend_service_slug" {
- description = "The backend service slug."
- type = string
- default = ""
-}
-
-variable "basic_auth_enabled" {
- description = "The basic_auth switch."
- type = string
- default = ""
-}
-
-variable "basic_auth_password" {
- description = "The basic_auth password."
- type = string
- sensitive = true
- default = ""
-}
-
-variable "basic_auth_username" {
- description = "The basic_auth username."
- type = string
- default = ""
-}
-
-variable "database_dumps_enabled" {
- description = "Enable database dumps."
- type = bool
- default = false
-}
-
-variable "env_slug" {
- description = "The environment slug (e.g. 'prod')."
- type = string
-}
-
-variable "frontend_service_extra_traefik_middlewares" {
- description = "The frontend service additional Traefik middlewares."
- type = list(string)
- default = []
-}
-
-variable "frontend_service_paths" {
- description = "The frontend service paths."
- type = list(string)
- default = []
-}
-
-variable "frontend_service_port" {
- description = "The frontend service port."
- type = number
- default = 3000
-}
-
-variable "frontend_service_slug" {
- description = "The frontend service slug."
- type = string
- default = ""
-}
-
-variable "grafana_password" {
- description = "The Grafana admin password."
- type = string
- sensitive = true
- default = ""
-}
-
-variable "grafana_persistence_enabled" {
- description = "Enable grafana persistence."
- type = bool
- default = false
-}
-
-variable "grafana_user" {
- description = "The Grafana admin username."
- type = string
- default = "admin"
-}
-
-variable "grafana_version" {
- description = "The Grafana version."
- type = string
- default = "9.4.1"
-}
-
-variable "kubernetes_cluster_ca_certificate" {
- description = "The base64 encoded Kubernetes CA certificate."
- type = string
- sensitive = true
-}
-
-variable "kubernetes_host" {
- description = "The Kubernetes host."
- type = string
-}
-
-variable "kubernetes_token" {
- description = "A Kubernetes admin token."
- type = string
- sensitive = true
-}
-
-variable "letsencrypt_certificate_email" {
- description = "The email used to issue the Let's Encrypt certificate."
- type = string
- default = ""
-}
-
-variable "letsencrypt_server" {
- description = "The Let's Encrypt server used to generate certificates."
- type = string
- default = ""
-}
-
-variable "monitoring_subdomain" {
- description = "The monitoring subdomain."
- type = string
- default = ""
-}
-
-variable "postgres_image" {
- description = "The Postgres Docker image."
- type = string
- default = "postgres:14"
-}
-
-variable "postgres_persistent_volume_capacity" {
- description = "The persistent volume capacity (e.g. 1Gi)."
- type = string
- default = "10Gi"
-}
-
-variable "postgres_persistent_volume_claim_capacity" {
- description = "The persistent volume claim capacity (e.g. 1Gi)."
- type = string
- default = "10Gi"
-}
-
-variable "postgres_persistent_volume_host_path" {
- description = "The persistent volume host path."
- type = string
-}
-
-variable "project_domain" {
- description = "The project domain."
- type = string
-}
-
-variable "project_slug" {
- description = "The project slug."
- type = string
-}
-
-variable "redis_image" {
- description = "The Redis Docker image."
- type = string
- default = "redis:6"
-}
-
-variable "registry_password" {
- description = "The Docker image registry password."
- type = string
- sensitive = true
-}
-
-variable "registry_server" {
- description = "The Docker image registry server."
- type = string
-}
-
-variable "registry_username" {
- description = "The Docker image registry username."
- type = string
- sensitive = true
-}
-
-variable "s3_access_id" {
- description = "The S3 bucket access key ID."
- type = string
- default = ""
- sensitive = true
-}
-
-variable "s3_bucket_name" {
- description = "The S3 bucket name."
- type = string
- default = ""
-}
-
-variable "s3_host" {
- description = "The S3 bucket host."
- type = string
- default = ""
-}
-
-variable "s3_region" {
- description = "The S3 bucket region."
- type = string
- default = ""
-}
-
-variable "s3_secret_key" {
- description = "The S3 bucket secret access key."
- type = string
- default = ""
- sensitive = true
-}
-
-variable "secondary_domains" {
- description = "An optional list of secondary domains to redirect to the main one."
- type = list(string)
- default = []
-}
-
-variable "stack_slug" {
- description = "The stack slug (e.g. 'main')."
- type = string
-}
-
-variable "subdomains" {
- description = "The subdomains associated to the environment."
- type = list(string)
- default = []
-
- validation {
- condition = length(var.subdomains) > 0
- error_message = "At least one subdomain must be specified."
- }
-}
-
-variable "tls_certificate_crt" {
- description = "The base64-encoded PEM-formatted TLS full certificate."
- type = string
- sensitive = true
- default = ""
-}
-
-variable "tls_certificate_key" {
- description = "The base64-encoded PEM-formatted TLS private key."
- type = string
- sensitive = true
- default = ""
-}
-
-
-variable "use_redis" {
- description = "Tell if a Redis service is used."
- type = bool
- default = false
-}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/environment/other-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/environment/other-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}"
deleted file mode 100644
index 4ca44e9b..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/environment/other-k8s/{% if cookiecutter.terraform_backend == \"gitlab\" %}backend.tf{% endif %}"
+++ /dev/null
@@ -1,4 +0,0 @@
-terraform {
- backend "http" {
- }
-}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/environment/other-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/environment/other-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}"
deleted file mode 100644
index 3849a361..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/environment/other-k8s/{% if cookiecutter.terraform_backend == \"terraform-cloud\" %}cloud.tf{% endif %}"
+++ /dev/null
@@ -1,9 +0,0 @@
-terraform {
- cloud {
- organization = "{{ cookiecutter.terraform_cloud_organization }}"
-
- workspaces {
- tags = ["project:{{ cookiecutter.project_slug }}"]
- }
- }
-}
diff --git a/{{cookiecutter.project_dirname}}/terraform/environment/vars/.tfvars b/{{cookiecutter.project_dirname}}/terraform/environment/vars/.tfvars
deleted file mode 100644
index e2ec1153..00000000
--- a/{{cookiecutter.project_dirname}}/terraform/environment/vars/.tfvars
+++ /dev/null
@@ -1,8 +0,0 @@
-{% if "environment" in cookiecutter.tfvars %}{% for item in cookiecutter.tfvars.environment|sort %}{{ item }}
-{% endfor %}{% endif %}# database_connection_pool_size=1
-# database_dumps_enabled=true
-# basic_auth_enabled=false
-# backend_service_extra_traefik_middlewares=[]
-# frontend_service_extra_traefik_middlewares=[]
-# grafana_persistence_enabled=false
-# use_postgis=true
diff --git "a/{{cookiecutter.project_dirname}}/terraform/environment/vars/{% if \"environment_dev\" in cookiecutter.tfvars %}dev.tfvars{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/environment/vars/{% if \"environment_dev\" in cookiecutter.tfvars %}dev.tfvars{% endif %}"
deleted file mode 100644
index e2140f3d..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/environment/vars/{% if \"environment_dev\" in cookiecutter.tfvars %}dev.tfvars{% endif %}"
+++ /dev/null
@@ -1,2 +0,0 @@
-{% for item in cookiecutter.tfvars.environment_dev|sort %}{{ item }}
-{% endfor %}
diff --git "a/{{cookiecutter.project_dirname}}/terraform/environment/vars/{% if \"environment_prod\" in cookiecutter.tfvars %}prod.tfvars{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/environment/vars/{% if \"environment_prod\" in cookiecutter.tfvars %}prod.tfvars{% endif %}"
deleted file mode 100644
index acdc2d7d..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/environment/vars/{% if \"environment_prod\" in cookiecutter.tfvars %}prod.tfvars{% endif %}"
+++ /dev/null
@@ -1,3 +0,0 @@
-{% for item in cookiecutter.tfvars.environment_prod|sort %}{{ item }}
-{% endfor %}# grafana_user="admin"
-# grafana_version="9.4.1"
diff --git "a/{{cookiecutter.project_dirname}}/terraform/environment/vars/{% if \"environment_stage\" in cookiecutter.tfvars %}stage.tfvars{% endif %}" "b/{{cookiecutter.project_dirname}}/terraform/environment/vars/{% if \"environment_stage\" in cookiecutter.tfvars %}stage.tfvars{% endif %}"
deleted file mode 100644
index 11fc6807..00000000
--- "a/{{cookiecutter.project_dirname}}/terraform/environment/vars/{% if \"environment_stage\" in cookiecutter.tfvars %}stage.tfvars{% endif %}"
+++ /dev/null
@@ -1,2 +0,0 @@
-{% for item in cookiecutter.tfvars.environment_stage|sort %}{{ item }}
-{% endfor %}
diff --git a/{{cookiecutter.project_dirname}}/traefik/20tab.crt b/{{cookiecutter.project_dirname}}/traefik/20tab.crt
deleted file mode 100644
index 496d6489..00000000
--- a/{{cookiecutter.project_dirname}}/traefik/20tab.crt
+++ /dev/null
@@ -1,20 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDOTCCAiGgAwIBAgIIEsVyFKfAXZ4wDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UE
-AxMMMjB0YWIgMTJjNTcyMCAXDTIyMDQyNjE1NTIwM1oYDzIxMjIwNDI2MTU1MjAz
-WjAXMRUwEwYDVQQDEwwyMHRhYiAxMmM1NzIwggEiMA0GCSqGSIb3DQEBAQUAA4IB
-DwAwggEKAoIBAQDIPXVRVctc/z71mto/z10BDvsRxVviXky71zxfnND9KVFbCiUM
-wdKMKsYGDCEXLYyaN8jMEqrZqbLZx4QRdffsiInfmzMyd/F54YP6uIdvelxBpCiZ
-vgvMtkK4y1/hk6sVfF+BMPXK0N2EOroYocahFDM/OdeSTmI8a2J4l0Clv6iTk8l5
-Bk4s+wrCi0zipx0lTYvrLR9OqXjZAyHqLMrSLTVSTXZTaWWCW0Rzpof3g/zKwe05
-8WwJA8puT7mpqsEeTLJ1J+0GPQQgM2JF2xy6cibmUm7C+zVcUF0esAu/VhJoXljR
-tzLUtHRDzGrZ8a/c0ylLoUxLeNSvH3audfxrAgMBAAGjgYYwgYMwDgYDVR0PAQH/
-BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8E
-CDAGAQH/AgEAMB0GA1UdDgQWBBTvDQRVL0dQipR++O+tbrXkmZKB+jAfBgNVHSME
-GDAWgBTvDQRVL0dQipR++O+tbrXkmZKB+jANBgkqhkiG9w0BAQsFAAOCAQEAKbtQ
-Gs3ucyv43nQRTVdJSoHhsGGn7GLbXOqIsTM8FvoA0/k6nsz112uXZp5Klnsk8pNC
-HEG/QpIowVshUsNRgIMQnJ35jLJgDy8ufGr+jXS+saV0r8jrBGbUp8GwLq+5XiMl
-07mddnbCL8/ukTCxnFDiDz4RtUzJ9DFgmuAF70vrjwPHKyYquxsXFFWaVYzzqdGm
-ekQzvwVIsF9KbFX527CFv/kexgaIqVDc31HF9OxjvmigRvBq1DN4DQ1vxvn8fd4i
-4PzrFWPeSq3MTWeNSSVsEevLNvjYbuUuDVEuYm1Bvr+OtrKIKmk4Zcp0np0/OxiO
-0pQWZpkhDhdOSymRsA==
------END CERTIFICATE-----
diff --git a/{{cookiecutter.project_dirname}}/traefik/conf/dynamic.yaml b/{{cookiecutter.project_dirname}}/traefik/conf/dynamic.yaml
deleted file mode 100644
index f8e4da0b..00000000
--- a/{{cookiecutter.project_dirname}}/traefik/conf/dynamic.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
-http:
- routers:{% if cookiecutter.frontend_type != 'none' %}{% if cookiecutter.backend_type != 'none' %}
- {{ cookiecutter.backend_service_slug }}:
- rule: |
- PathPrefix(`/__debug__`) ||
- PathPrefix(`/admin`) ||
- PathPrefix(`/api`) ||{% if cookiecutter.media_storage == 'local' %}
- PathPrefix(`/media`) ||{% endif %}
- PathPrefix(`/static`)
- service: {{ cookiecutter.backend_service_slug }}{% endif %}
- {{ cookiecutter.frontend_service_slug }}:
- rule: PathPrefix(`/`)
- service: {{ cookiecutter.frontend_service_slug }}{% else %}
- {{ cookiecutter.backend_service_slug }}:
- rule: PathPrefix(`/`)
- service: {{ cookiecutter.backend_service_slug }}{% endif %}
- services:{% if cookiecutter.backend_type != 'none' %}
- {{ cookiecutter.backend_service_slug }}:
- loadBalancer:
- servers:
- - url: "http://{{ cookiecutter.backend_service_slug }}:8000/"{% endif %}{% if cookiecutter.frontend_type != 'none' %}
- {{ cookiecutter.frontend_service_slug }}:
- loadBalancer:
- servers:
- - url: "http://{{ cookiecutter.frontend_service_slug }}:3000/"{% endif %}
-
-tls:
- certificates:
- - certFile: /traefik/localhost/cert.pem
- keyFile: /traefik/localhost/key.pem
diff --git a/{{cookiecutter.project_dirname}}/traefik/conf/static.yaml b/{{cookiecutter.project_dirname}}/traefik/conf/static.yaml
deleted file mode 100644
index 1bd3345b..00000000
--- a/{{cookiecutter.project_dirname}}/traefik/conf/static.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-api:
- insecure: true
- dashboard: true
- debug: true
-
-entryPoints:
- websecure:
- address: ":8443"
- http:
- tls:
- options:
-
-log:
- level: ERROR
-
-ping: true
-
-providers:
- file:
- filename: /traefik/conf/dynamic.yaml
diff --git a/{{cookiecutter.project_dirname}}/traefik/localhost/cert.pem b/{{cookiecutter.project_dirname}}/traefik/localhost/cert.pem
deleted file mode 100644
index e7b19d8d..00000000
--- a/{{cookiecutter.project_dirname}}/traefik/localhost/cert.pem
+++ /dev/null
@@ -1,19 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDKTCCAhGgAwIBAgIIfSf3XjCARUcwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UE
-AxMMMjB0YWIgMTJjNTcyMB4XDTIyMDQyNjE1NTIwM1oXDTI0MDUyNjE1NTIwM1ow
-FDESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
-CgKCAQEAuOmA+UuNb+Jd55ovJXo215cG5RZQCQCn0TERijlb181swfG77XGXauG6
-zq3pOsepVoA4OUdHfiHhwFU6AQRrFxHOwyp/pPcmm7LCamMJvqhcMTdiEu88/Skp
-QTNwrczwCY6decmLI8n8w75EkprwSQn5tnaf3RbnfaZdRq2LG2mrZ5svn6GiqjGP
-OqOe+EX4gMuLiJCNaCIb6fDUrqn1cnd2nkZ8cE2gMT6fUWhFwguqD5J3IuSgbsH3
-Uis1CkH4lmJkfMvJF56WtqGgOzDVcCbJJzOzJ2zLiOsHmBsCXpWItuUKSyL2JQbX
-54Kk1heYZ5LOj/pAKBHcwN8znteIrQIDAQABo3wwejAOBgNVHQ8BAf8EBAMCBaAw
-HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHwYD
-VR0jBBgwFoAU7w0EVS9HUIqUfvjvrW615JmSgfowGgYDVR0RBBMwEYIJbG9jYWxo
-b3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQBB8IJlididZ+9WZW/LWZA/bspH
-hTVDYPyBB5bGFMw9BmJdOWTntIOj3J8FF1MLfWD2wLLQOetJ7AHWfPOprH+P0yNX
-G4i6dynMatCXRoKsNsS7JTlE+Y3mk0hapTQvW91KT+T1VheQcQiiTpwHUSaf90Mp
-7UI/yG9uVxVa75Dv4WSmpOXo4GrWVpzRCqlCAsRuT2nqgIq1cCOhIVmUtPyd0xlS
-SxAv0evWuK5pKq8tOgPUKiQCeaoxIrQSWRKhxenaib1kNhkQsEhEvWHNebue0pq5
-0pA3Y0PTEDr87DcU/sCfs+R7fJkwdeM0J+AUKxn7J3FeUPMar+vs2TRKwQbw
------END CERTIFICATE-----
diff --git a/{{cookiecutter.project_dirname}}/traefik/localhost/key.pem b/{{cookiecutter.project_dirname}}/traefik/localhost/key.pem
deleted file mode 100644
index 340ac4e7..00000000
--- a/{{cookiecutter.project_dirname}}/traefik/localhost/key.pem
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEAuOmA+UuNb+Jd55ovJXo215cG5RZQCQCn0TERijlb181swfG7
-7XGXauG6zq3pOsepVoA4OUdHfiHhwFU6AQRrFxHOwyp/pPcmm7LCamMJvqhcMTdi
-Eu88/SkpQTNwrczwCY6decmLI8n8w75EkprwSQn5tnaf3RbnfaZdRq2LG2mrZ5sv
-n6GiqjGPOqOe+EX4gMuLiJCNaCIb6fDUrqn1cnd2nkZ8cE2gMT6fUWhFwguqD5J3
-IuSgbsH3Uis1CkH4lmJkfMvJF56WtqGgOzDVcCbJJzOzJ2zLiOsHmBsCXpWItuUK
-SyL2JQbX54Kk1heYZ5LOj/pAKBHcwN8znteIrQIDAQABAoIBAQCvBdXeIXUugcUA
-gyA9CQD/7yPHkucOnmA50YgYnVIhYX+f039dsDbyI9b96f7odDQEfDi7foxdBxT3
-R7QvYk869tAOSxumuYkBtqHusO94V0NsEk/yPCAuP9ecr2PoHiLV6PeFXw8kQPvP
-Czk8ywrFF3wVCRMn4ZYbiGyhiYIx3qV9fS4rmyjDxLT1sXH8ciRjgiHT1qWjS2Fh
-RoN6ITZxAnru9US75H9HJxa+FCy/oJ6gt8IWE4W/QUqijZM/g6+DdwfyzJ7riUfN
-px4wMEOEM+EjutDQqyVbaoy/OWZGKYFI2MnKt/Mtv7k0wA0W2UUCAw9jtuHO/cuP
-up1BlLYBAoGBAMYVRe7eNjgnayQEvdhv4gxw8Rcy4X2DEnR6jFqXYPp2HUjmWRUC
-Q4SsWucoAIeW8UQjYDP+j5IDM/f/UFtwbOU3FIofrd2453moBC6mbIKRUGeeQ1Gq
-9FVLwJr12aHmpNbWLdie/+zSFK9pDRd0OZC6hrRMS6UsoK2k6g8brM51AoGBAO76
-XpTGqdzhRmpw9+b2YUcHTvnjvxGBJyQ/F3c/vgzOBzHwQB/66QvSYgtWuR0avQnN
-/nubXKrEzHzD/0Zdc69OgWAqt3/F0iwVWsUlP3SHDW2nMpcwBiNo4NUviP9GBalq
-i6tJN1ECcV7ApkZv59HQidGaWqhtnEIi3YQj4HpZAoGAV6zy11fkB9bqxXaT0Uk0
-dO1IyUvsIvHxKT1VAoQemL3PGIVcyL/HwuHHGqnKEjntcIt3+YujYK2qQwrvNon8
-qThIDxsWih1d16tWro9bWC+Zt0OF6JASte5hwjUvr0m3jKAgitFV1izmmv2Und0D
-3dux4/whP2sRc8qbDzTguEECgYEA042QbOPxOzexvkiDLFKvitFNeKnEWxqaK9wu
-ScKD25IHjI3CNo1IAM8dPCxpcvpYnnVc8s92GTZeT5SyRvgzkN1OanNmPhZBGAVP
-dXaj1eQ4XvnEL1K0HGSbpB2QiWrTWEaZnegSsAQZmGeyymgGMBcL6iFaX/+odGOo
-9XFNJwECgYA99WnDQnpfP8Ku3OSTDhlagZP2UzrF0s3nMgYHUv2FSAYtbWsIXJap
-hbh9J1C9+x2li5MMm6jIaY/8Uh7H0NnQ6K16PgMT20vLrM6i4fzX/mTsSLTO+3Rq
-MLgyGvJw6BSQFXUlKD9P55iNtPP1Vrft5PRXOulXC6Gqis3f8ACx4A==
------END RSA PRIVATE KEY-----
diff --git a/{{cookiecutter.project_dirname}}/vault-project.tfvars.example b/{{cookiecutter.project_dirname}}/vault-project.tfvars.example
new file mode 100644
index 00000000..20ba8fc5
--- /dev/null
+++ b/{{cookiecutter.project_dirname}}/vault-project.tfvars.example
@@ -0,0 +1,9 @@
+{%- set svc_list = [] %}
+{%- if cookiecutter.backend_type != 'none' %}{% set _ = svc_list.append(cookiecutter.backend_service_slug) %}{% endif %}
+{%- if cookiecutter.frontend_type != 'none' %}{% set _ = svc_list.append(cookiecutter.frontend_service_slug) %}{% endif %}
+project_admin_users = []
+project_name = "{{ cookiecutter.project_name }}"
+project_namespace_path = ""
+project_slug = "{{ cookiecutter.project_slug }}"
+services = {{ svc_list | tojson }}
+vault_address = "https://vault.20tab.com/"