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/"