Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 7 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,14 @@ Project name: My Project Name
Project slug [my-project-name]:
Service slug [frontend]:
Project dirname (frontend, myprojectname) [frontend]: myprojectname
Deploy type (digitalocean-k8s, other-k8s) [digitalocean-k8s]:
Terraform backend (gitlab, terraform-cloud) [terraform-cloud]:
Terraform host name [app.terraform.io]:
Terraform Cloud User token:
Terraform Organization: my-organization-name
Do you want to create Terraform Cloud Organization 'my-organization-name'? [y/N]:
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
(1, 2, 3) [1]:
Cluster slug hosting the 'development' environment [dev]:
Cluster slug hosting the 'staging' environment [dev]:
Cluster slug hosting the 'production' environment [main]:
Development environment complete URL [https://dev.my-project-name.com/]:
Staging environment complete URL [https://stage.my-project-name.com/]:
Production environment complete URL [https://www.my-project-name.com/]:
Expand All @@ -98,6 +95,8 @@ Initializing the frontend service:
...creating the Terraform Cloud resources
```

The generated service deploys via the [Minos service module](https://gitlab.com/20tab-open/minos) using the `registry.gitlab.com/20tab-open/minos/service:latest` image and the OpenTofu GitLab Components (`opentofu/apply@3.11.0`). Each environment (`development`, `staging`, `production`) is mapped to a Kubernetes cluster slug via the prompts above.

## 🗒️ Arguments

The following arguments can be appended to the Docker and shell commands
Expand Down Expand Up @@ -142,13 +141,6 @@ The following arguments can be appended to the Docker and shell commands

### 📐 Architecture

#### Deploy type

| Description | Argument |
| ----------------------- | ------------------------------------ |
| DigitalOcean Kubernetes | `--deployment-type=digitalocean-k8s` |
| Other Kubernetes | `--deployment-type=other-k8s` |

#### Terraform backend

| Name | Argument |
Expand All @@ -170,14 +162,9 @@ The following arguments can be appended to the Docker and shell commands
Disabled args
`--terraform-cloud-organization-create-skip`

#### Environment distribution
#### Env-to-cluster mapping

Choose the environments distribution:
Value | Description | Argument
------------- | ------------- | -------------
1 | All environments share the same stack (Default) | `--environment-distribution=1`
2 | Dev and Stage environments share the same stack, Prod has its own | `--environment-distribution=2`
3 | Each environment has its own stack | `--environment-distribution=3`
Each of the three environments (`development`, `staging`, `production`) is mapped to a Kubernetes cluster slug. Defaults: `dev` for development and staging, `main` for production. The mapping can be set non-interactively via repeated `--env-to-cluster` flags or supplied programmatically as a `dict[str, str]`.

#### Project Domain

Expand Down
75 changes: 42 additions & 33 deletions bootstrap/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
from slugify import slugify

from bootstrap.constants import (
DEPLOYMENT_TYPE_CHOICES,
DEPLOYMENT_TYPE_DIGITALOCEAN,
DEPLOYMENT_TYPE_OTHER,
ENVIRONMENTS_DISTRIBUTION_CHOICES,
ENVIRONMENTS_DISTRIBUTION_DEFAULT,
ENVIRONMENTS_DISTRIBUTION_PROMPT,
ENV_NAMES,
ENV_TO_CLUSTER_DEFAULT,
GITLAB_URL_DEFAULT,
MINOS_SERVICE_IMAGE,
NODE_VERSION_DEFAULT,
OPENTOFU_COMPONENT_VERSION,
OPENTOFU_VERSION,
TERRAFORM_BACKEND_CHOICES,
TERRAFORM_BACKEND_TFC,
)
Expand All @@ -42,7 +42,6 @@ class Collector:
service_slug: str | None = None
internal_backend_url: str | None = None
internal_service_port: int | None = None
deployment_type: str | None = None
terraform_backend: str | None = None
terraform_cloud_hostname: str | None = None
terraform_cloud_token: str | None = None
Expand All @@ -51,7 +50,7 @@ class Collector:
terraform_cloud_admin_email: str | None = None
vault_token: str | None = None
vault_url: str | None = None
environments_distribution: str | None = None
env_to_cluster: dict[str, str] | None = None
project_url_dev: str | None = None
project_url_stage: str | None = None
project_url_prod: str | None = None
Expand All @@ -62,6 +61,10 @@ class Collector:
gitlab_url: str | None = None
gitlab_token: str | None = None
gitlab_namespace_path: str | None = None
node_version: str | None = None
minos_service_image: str | None = None
opentofu_component_version: str | None = None
opentofu_version: str | None = None
uid: int | None = None
gid: int | None = None
terraform_dir: Path | None = None
Expand All @@ -81,11 +84,11 @@ def collect(self):
self.set_use_redis()
self.set_terraform()
self.set_vault()
self.set_deployment_type()
self.set_environments_distribution()
self.set_env_to_cluster()
self.set_project_urls()
self.set_sentry()
self.set_gitlab()
self.set_versions()

def set_project_slug(self):
"""Set the project slug option."""
Expand Down Expand Up @@ -189,27 +192,15 @@ 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_env_to_cluster(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 not in self.env_to_cluster:
self.env_to_cluster[env_name] = click.prompt(
f"Cluster slug hosting the '{env_name}' environment",
default=ENV_TO_CLUSTER_DEFAULT[env_name],
)

def set_project_urls(self):
"""Set the project urls options."""
Expand Down Expand Up @@ -274,6 +265,21 @@ def set_gitlab(self):
)
)

def set_versions(self):
"""Set the toolchain versions."""
self.node_version = self.node_version or click.prompt(
"Node version", default=NODE_VERSION_DEFAULT
)
self.minos_service_image = self.minos_service_image or click.prompt(
"Minos service image", default=MINOS_SERVICE_IMAGE
)
self.opentofu_component_version = self.opentofu_component_version or click.prompt(
"OpenTofu CI component version", default=OPENTOFU_COMPONENT_VERSION
)
self.opentofu_version = self.opentofu_version or click.prompt(
"OpenTofu version", default=OPENTOFU_VERSION
)

def get_runner(self):
"""Get the bootstrap runner instance."""
return Runner(
Expand All @@ -287,7 +293,6 @@ def get_runner(self):
service_slug=self.service_slug,
internal_backend_url=self.internal_backend_url,
internal_service_port=self.internal_service_port,
deployment_type=self.deployment_type,
terraform_backend=self.terraform_backend,
terraform_cloud_hostname=self.terraform_cloud_hostname,
terraform_cloud_token=self.terraform_cloud_token,
Expand All @@ -296,7 +301,7 @@ def get_runner(self):
terraform_cloud_admin_email=self.terraform_cloud_admin_email,
vault_token=self.vault_token,
vault_url=self.vault_url,
environments_distribution=self.environments_distribution,
env_to_cluster=self.env_to_cluster,
project_url_dev=self.project_url_dev,
project_url_stage=self.project_url_stage,
project_url_prod=self.project_url_prod,
Expand All @@ -307,6 +312,10 @@ def get_runner(self):
gitlab_url=self.gitlab_url,
gitlab_token=self.gitlab_token,
gitlab_namespace_path=self.gitlab_namespace_path,
node_version=self.node_version,
minos_service_image=self.minos_service_image,
opentofu_component_version=self.opentofu_component_version,
opentofu_version=self.opentofu_version,
terraform_dir=self.terraform_dir,
logs_dir=self.logs_dir,
)
Expand Down
86 changes: 29 additions & 57 deletions bootstrap/constants.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,6 @@
"""Web project initialization CLI constants."""


# Stacks

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

DEV_STACK_NAME = "development"

DEV_STACK_SLUG = "dev"

STAGE_STACK_NAME = "staging"

STAGE_STACK_SLUG = "stage"

MAIN_STACK_NAME = "main"

MAIN_STACK_SLUG = "main"

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

# Environments

# BEWARE: environment names must be suitable for inclusion in Vault paths
Expand All @@ -38,59 +9,60 @@

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

GITLAB_TOKEN_ENV_VAR = "GITLAB_PRIVATE_TOKEN"

VAULT_TOKEN_ENV_VAR = "VAULT_TOKEN"

# Deployment type
# Terraform backend

TERRAFORM_BACKEND_GITLAB = "gitlab"

DEPLOYMENT_TYPE_DIGITALOCEAN = "digitalocean-k8s"
TERRAFORM_BACKEND_TFC = "terraform-cloud"

DEPLOYMENT_TYPE_OTHER = "other-k8s"
TERRAFORM_BACKEND_CHOICES = [TERRAFORM_BACKEND_GITLAB, TERRAFORM_BACKEND_TFC]

DEPLOYMENT_TYPE_CHOICES = [DEPLOYMENT_TYPE_DIGITALOCEAN, DEPLOYMENT_TYPE_OTHER]
# GitLab

# Environments distribution
GITLAB_URL_DEFAULT = "https://gitlab.com"

ENVIRONMENTS_DISTRIBUTION_DEFAULT = "1"
# Clusters

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

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
"""
CLUSTER_MAIN_SLUG = "main"

# Terraform backend
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,
}

TERRAFORM_BACKEND_GITLAB = "gitlab"
# Vault

TERRAFORM_BACKEND_TFC = "terraform-cloud"
VAULT_SERVICE_ROLE = "service-gitlab-job"

TERRAFORM_BACKEND_CHOICES = [TERRAFORM_BACKEND_GITLAB, TERRAFORM_BACKEND_TFC]
# Minos

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

GITLAB_URL_DEFAULT = "https://gitlab.com"
# OpenTofu

OPENTOFU_COMPONENT_VERSION = "3.11.0"

OPENTOFU_VERSION = "1.10.6"

# Node

NODE_VERSION_DEFAULT = "24.14.0"
Loading
Loading