From 0b048b08d6e95d563944404d1f99d4e17cb56c0b Mon Sep 17 00:00:00 2001 From: viniciusdc Date: Tue, 1 Oct 2024 11:00:02 -0300 Subject: [PATCH 1/3] include backup-restore service manifests --- .../template/backup-restore.tf | 0 .../services/backup-restore/ingress.tf | 26 ++++ .../services/backup-restore/main.tf | 134 ++++++++++++++++++ .../services/backup-restore/variables.tf | 20 +++ 4 files changed, 180 insertions(+) create mode 100644 src/_nebari/stages/kubernetes_services/template/backup-restore.tf create mode 100644 src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/ingress.tf create mode 100644 src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/main.tf create mode 100644 src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/variables.tf diff --git a/src/_nebari/stages/kubernetes_services/template/backup-restore.tf b/src/_nebari/stages/kubernetes_services/template/backup-restore.tf new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/ingress.tf b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/ingress.tf new file mode 100644 index 0000000000..7c4a4e46b5 --- /dev/null +++ b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/ingress.tf @@ -0,0 +1,26 @@ +resource "kubernetes_manifest" "backup-restore-api" { + manifest = { + apiVersion = "traefik.containo.us/v1alpha1" + kind = "IngressRoute" + metadata = { + name = "backup-restore-api" + namespace = var.namespace + } + spec = { + entryPoints = ["backup-restore"] + routes = [ + { + kind = "Rule" + match = "Host(`${var.external-url}`)" + services = [ + { + name = helm_release.backup_restore.name + port = 9000 + namespace = var.namespace + } + ] + } + ] + } + } +} diff --git a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/main.tf b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/main.tf new file mode 100644 index 0000000000..e878cddb74 --- /dev/null +++ b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/main.tf @@ -0,0 +1,134 @@ +resource "random_password" "backup_restore_service_token" { + for_each = var.clients + length = 32 + special = false +} + +resource "kubernetes_service" "backup_restore" { + metadata { + name = "backup-restore" + namespace = var.namespace + } + + spec { + selector = { + app = "backup-restore" + } + + port { + port = 8000 + protocol = "TCP" + } + } +} + +resource "kubernetes_service_account" "backup_restore" { + metadata { + name = "backup-restore" + namespace = var.namespace + } +} + +resource "kubernetes_manifest" "backup_restore" { + manifest = { + apiVersion = "traefik.containo.us/v1alpha1" + kind = "IngressRoute" + metadata = { + name = "backup-restore" + namespace = var.namespace + } + spec = { + entryPoints = ["websecure"] + routes = [ + { + kind = "Rule" + match = "Host(`${var.external-url}`) && PathPrefix(`/backup-restore/`)" + + middlewares = [ + { + name = "nebari-backup-restore-api" + namespace = var.namespace + } + ] + + services = [ + { + name = kubernetes_service.gateway.metadata.0.name + port = 8000 + } + ] + } + ] + } + } +} + +resource "kubernetes_secret" "backup_restore_service_token" { + for_each = var.clients + metadata { + name = "backup-restore-${each.key}" + namespace = var.namespace + } + + data = { + token = random_password.backup_restore_service_token[each.key].result + } +} + +resource "kubernetes_deployment" "backup_restore" { + metadata { + name = "backup-restore" + namespace = var.namespace + } + + spec { + replicas = 1 + + selector { + match_labels = { + app = "backup-restore" + } + } + + template { + metadata { + labels = { + app = "backup-restore" + } + } + + spec { + service_account_name = kubernetes_service_account.backup_restore.metadata.0.name + + container { + name = "backup-restore" + image = "nebari/backup-restore:latest" + + env { + name = "BACKUP_RESTORE_TOKEN" + value = kubernetes_secret.backup_restore_service_token[each.key].data["token"] + } + + env { + name = "BACKUP_RESTORE_NAMESPACE" + value = var.namespace + } + + env { + name = "BACKUP_RESTORE_EXTERNAL_URL" + value = var.external-url + } + + env { + name = "BACKUP_RESTORE_CLIENT" + value = each.key + } + + port { + container_port = 8000 + } + } + } + } + } +} diff --git a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/variables.tf b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/variables.tf new file mode 100644 index 0000000000..9e996e027d --- /dev/null +++ b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/variables.tf @@ -0,0 +1,20 @@ +variable "name" { + description = "Name prefix to deploy backup-restore server" + type = string + default = "nebari" +} + +variable "namespace" { + description = "Namespace to deploy backup-restore server" + type = string +} + +variable "external-url" { + description = "External url that jupyterhub cluster is accessible" + type = string +} + +variable "clients" { + description = "List of clients that can access the backup-restore server by API" + type = list(string) +} From 17490565c520f26c06000c05082075057cea6832 Mon Sep 17 00:00:00 2001 From: viniciusdc Date: Thu, 3 Oct 2024 15:15:07 -0300 Subject: [PATCH 2/3] add secrets/updates & add force_unlock for debugging --- src/_nebari/deploy.py | 17 +++- src/_nebari/provider/terraform.py | 16 ++++ src/_nebari/stages/base.py | 6 ++ src/_nebari/stages/infrastructure/__init__.py | 7 +- .../stages/kubernetes_keycloak/__init__.py | 7 +- .../stages/kubernetes_services/__init__.py | 8 ++ .../template/backup-restore.tf | 29 ++++++ .../services/backup-restore/ingress.tf | 2 +- .../services/backup-restore/main.tf | 89 +++++++++++++------ .../services/backup-restore/variables.tf | 15 ++++ .../stages/terraform_state/__init__.py | 12 ++- src/_nebari/subcommands/deploy.py | 6 ++ src/nebari/hookspecs.py | 5 +- 13 files changed, 183 insertions(+), 36 deletions(-) diff --git a/src/_nebari/deploy.py b/src/_nebari/deploy.py index 4478e65f75..ae424061d8 100644 --- a/src/_nebari/deploy.py +++ b/src/_nebari/deploy.py @@ -15,6 +15,7 @@ def deploy_configuration( stages: List[hookspecs.NebariStage], disable_prompt: bool = False, disable_checks: bool = False, + stage_force_unlock: str = None, ) -> Dict[str, Any]: if config.prevent_deploy: raise ValueError( @@ -47,12 +48,26 @@ def deploy_configuration( with timer(logger, "deploying Nebari"): stage_outputs = {} + force_unlock = False with contextlib.ExitStack() as stack: for stage in stages: s: hookspecs.NebariStage = stage( output_directory=pathlib.Path.cwd(), config=config ) - stack.enter_context(s.deploy(stage_outputs, disable_prompt)) + print(f"Deploying stage {s.name}") + print("stage_force_unlock", stage_force_unlock) + if stage_force_unlock: + _unlock_stage_name, _unlock_state_id = stage_force_unlock.split(":") + if _unlock_stage_name == s.name: + force_unlock = _unlock_state_id + print(f"Force unlocking stage {s.name} :: {force_unlock}") + stack.enter_context( + s.deploy( + stage_outputs=stage_outputs, + disable_prompt=disable_prompt, + force_unlock=force_unlock, + ) + ) if not disable_checks: s.check(stage_outputs, disable_prompt) diff --git a/src/_nebari/provider/terraform.py b/src/_nebari/provider/terraform.py index 59d88e76dd..808cebddb8 100644 --- a/src/_nebari/provider/terraform.py +++ b/src/_nebari/provider/terraform.py @@ -28,6 +28,7 @@ def deploy( terraform_import: bool = False, terraform_apply: bool = True, terraform_destroy: bool = False, + terraform_lock_id: str = None, input_vars: Dict[str, Any] = {}, state_imports: List[Any] = [], ): @@ -46,6 +47,10 @@ def deploy( terraform_destroy: whether to run `terraform destroy` default False + terraform_lock_id: Used to toggle the force-unlock feature of a given stage based + on the provided lock_id. + None + input_vars: supply values for "variable" resources within terraform module @@ -61,6 +66,9 @@ def deploy( if terraform_init: init(directory) + if terraform_lock_id: + force_unlock(directory, terraform_lock_id) + if terraform_import: for addr, id in state_imports: tfimport( @@ -139,6 +147,14 @@ def init(directory=None, upgrade=True): run_terraform_subprocess(command, cwd=directory, prefix="terraform") +def force_unlock(directory=None, lock_id=None): + logger.info(f"terraform unlock directory={directory}") + with timer(logger, "terraform unlock"): + run_terraform_subprocess( + ["force-unlock", "-force", lock_id], cwd=directory, prefix="terraform" + ) + + def apply(directory=None, targets=None, var_files=None): targets = targets or [] var_files = var_files or [] diff --git a/src/_nebari/stages/base.py b/src/_nebari/stages/base.py index cef1322e95..8d6cc9448b 100644 --- a/src/_nebari/stages/base.py +++ b/src/_nebari/stages/base.py @@ -284,6 +284,7 @@ def deploy( stage_outputs: Dict[str, Dict[str, Any]], disable_prompt: bool = False, terraform_init: bool = True, + force_unlock: bool = False, ): deploy_config = dict( directory=str(self.output_directory / self.stage_prefix), @@ -291,6 +292,11 @@ def deploy( terraform_init=terraform_init, ) state_imports = self.state_imports() + print("Deploying terraform resources for", self.name) + print("Force unlock:", force_unlock) + if force_unlock: + deploy_config["terraform_lock_id"] = force_unlock + if state_imports: deploy_config["terraform_import"] = True deploy_config["state_imports"] = state_imports diff --git a/src/_nebari/stages/infrastructure/__init__.py b/src/_nebari/stages/infrastructure/__init__.py index 026f33fe82..916930124f 100644 --- a/src/_nebari/stages/infrastructure/__init__.py +++ b/src/_nebari/stages/infrastructure/__init__.py @@ -923,9 +923,12 @@ def post_deploy( @contextlib.contextmanager def deploy( - self, stage_outputs: Dict[str, Dict[str, Any]], disable_prompt: bool = False + self, + stage_outputs: Dict[str, Dict[str, Any]], + disable_prompt: bool = False, + force_unlock: bool = False, ): - with super().deploy(stage_outputs, disable_prompt): + with super().deploy(stage_outputs, disable_prompt, force_unlock=force_unlock): with kubernetes_provider_context( stage_outputs["stages/" + self.name]["kubernetes_credentials"]["value"] ): diff --git a/src/_nebari/stages/kubernetes_keycloak/__init__.py b/src/_nebari/stages/kubernetes_keycloak/__init__.py index 7ded0f1f57..7e47c3e90d 100644 --- a/src/_nebari/stages/kubernetes_keycloak/__init__.py +++ b/src/_nebari/stages/kubernetes_keycloak/__init__.py @@ -297,9 +297,12 @@ def _attempt_keycloak_connection( @contextlib.contextmanager def deploy( - self, stage_outputs: Dict[str, Dict[str, Any]], disable_prompt: bool = False + self, + stage_outputs: Dict[str, Dict[str, Any]], + disable_prompt: bool = False, + force_unlock: bool = False, ): - with super().deploy(stage_outputs, disable_prompt): + with super().deploy(stage_outputs, disable_prompt, force_unlock=force_unlock): with keycloak_provider_context( stage_outputs["stages/" + self.name]["keycloak_credentials"]["value"] ): diff --git a/src/_nebari/stages/kubernetes_services/__init__.py b/src/_nebari/stages/kubernetes_services/__init__.py index bd4dfd759e..d1e6fd222e 100644 --- a/src/_nebari/stages/kubernetes_services/__init__.py +++ b/src/_nebari/stages/kubernetes_services/__init__.py @@ -190,6 +190,14 @@ def check_default(cls, value): return value +class BackupRestoreStorage(schema.Base): + type: str + + +class BackupRestore(schema.Base): + storage: BackupRestoreStorage = BackupRestoreStorage(type="s3") + + class CondaEnvironment(schema.Base): name: str channels: Optional[List[str]] = None diff --git a/src/_nebari/stages/kubernetes_services/template/backup-restore.tf b/src/_nebari/stages/kubernetes_services/template/backup-restore.tf index e69de29bb2..7cb010fc23 100644 --- a/src/_nebari/stages/kubernetes_services/template/backup-restore.tf +++ b/src/_nebari/stages/kubernetes_services/template/backup-restore.tf @@ -0,0 +1,29 @@ +variable "backup-restore-image" { + description = "The image to use for the backup-restore service" + default = "vcerutti/nebari-backup-restore" +} + +variable "backup-restore-image-tag" { + description = "The tag of the image to use for the backup-restore service" + default = "latest" +} + +variable "backup-restore-clients" { + description = "List of clients that can access the backup-restore server by API" + type = list(string) + default = ["nebari-cli"] +} + +module "kubernetes-backup-restore-server" { + source = "./modules/kubernetes/services/backup-restore" + + name = "nebari" + namespace = var.environment + + external-url = var.endpoint + realm_id = var.realm_id + + backup-restore-image = var.backup-restore-image + backup-restore-image-tag = var.backup-restore-image-tag + clients = var.backup-restore-clients +} diff --git a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/ingress.tf b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/ingress.tf index 7c4a4e46b5..72bfa45e61 100644 --- a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/ingress.tf +++ b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/ingress.tf @@ -14,7 +14,7 @@ resource "kubernetes_manifest" "backup-restore-api" { match = "Host(`${var.external-url}`)" services = [ { - name = helm_release.backup_restore.name + name = kubernetes_deployment.backup_restore.metadata.0.name port = 9000 namespace = var.namespace } diff --git a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/main.tf b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/main.tf index e878cddb74..b3333a9a51 100644 --- a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/main.tf +++ b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/main.tf @@ -1,9 +1,28 @@ +locals { + clients = { + for client in var.clients : client => client + } +} + resource "random_password" "backup_restore_service_token" { - for_each = var.clients + for_each = local.clients length = 32 special = false } +resource "kubernetes_secret" "backup_restore_service_token" { + for_each = local.clients + metadata { + name = "backup-restore-${each.key}" + namespace = var.namespace + } + + data = { + token = random_password.backup_restore_service_token[each.key].result + } +} + + resource "kubernetes_service" "backup_restore" { metadata { name = "backup-restore" @@ -22,6 +41,18 @@ resource "kubernetes_service" "backup_restore" { } } +resource "kubernetes_config_map" "backup-restore-etc" { + metadata { + name = "backup-restore-etc" + namespace = var.namespace + } + + data = { + "keycloak.json" = jsonencode({}) + "storage.json" = jsonencode({}) + } +} + resource "kubernetes_service_account" "backup_restore" { metadata { name = "backup-restore" @@ -53,7 +84,7 @@ resource "kubernetes_manifest" "backup_restore" { services = [ { - name = kubernetes_service.gateway.metadata.0.name + name = kubernetes_service.backup_restore.metadata.0.name port = 8000 } ] @@ -63,16 +94,18 @@ resource "kubernetes_manifest" "backup_restore" { } } -resource "kubernetes_secret" "backup_restore_service_token" { - for_each = var.clients - metadata { - name = "backup-restore-${each.key}" - namespace = var.namespace - } - data = { - token = random_password.backup_restore_service_token[each.key].result - } +module "jupyterhub-openid-client" { + source = "../keycloak-client" + + realm_id = var.realm_id + client_id = "nebari-cli" + external-url = var.external-url + role_mapping = {} + client_roles = [] + callback-url-paths = [] + service-accounts-enabled = true + service-account-roles = ["realm-admin"] } resource "kubernetes_deployment" "backup_restore" { @@ -101,33 +134,35 @@ resource "kubernetes_deployment" "backup_restore" { service_account_name = kubernetes_service_account.backup_restore.metadata.0.name container { - name = "backup-restore" - image = "nebari/backup-restore:latest" + name = "backup-restore" + image = "${var.backup-restore-image}:${var.backup-restore-image-tag}" + image_pull_policy = "Always" env { - name = "BACKUP_RESTORE_TOKEN" - value = kubernetes_secret.backup_restore_service_token[each.key].data["token"] + name = "CONFIG_DIR" + value = "/etc/backup-restore/config" } - env { - name = "BACKUP_RESTORE_NAMESPACE" - value = var.namespace - } - - env { - name = "BACKUP_RESTORE_EXTERNAL_URL" - value = var.external-url - } + command = ["backup-restore", "--standalone"] - env { - name = "BACKUP_RESTORE_CLIENT" - value = each.key + volume_mount { + name = "config" + mount_path = "/etc/backup-restore/config" + read_only = true } port { container_port = 8000 } } + + volume { + name = "config" + + config_map { + name = kubernetes_config_map.backup-restore-etc.metadata.0.name + } + } } } } diff --git a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/variables.tf b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/variables.tf index 9e996e027d..10c6e651ed 100644 --- a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/variables.tf +++ b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/variables.tf @@ -18,3 +18,18 @@ variable "clients" { description = "List of clients that can access the backup-restore server by API" type = list(string) } + +variable "realm_id" { + description = "Realm ID to use for authentication" + type = string +} + +variable "backup-restore-image" { + description = "Backup-restore image" + type = string +} + +variable "backup-restore-image-tag" { + description = "Version of backup-restore to use" + type = string +} diff --git a/src/_nebari/stages/terraform_state/__init__.py b/src/_nebari/stages/terraform_state/__init__.py index 37568be130..5edeec0e52 100644 --- a/src/_nebari/stages/terraform_state/__init__.py +++ b/src/_nebari/stages/terraform_state/__init__.py @@ -232,13 +232,21 @@ def input_vars(self, stage_outputs: Dict[str, Dict[str, Any]]): @contextlib.contextmanager def deploy( - self, stage_outputs: Dict[str, Dict[str, Any]], disable_prompt: bool = False + self, + stage_outputs: Dict[str, Dict[str, Any]], + disable_prompt: bool = False, + force_unlock: bool = False, ): self.check_immutable_fields() # No need to run terraform init here as it's being called when running the # terraform show command, inside check_immutable_fields - with super().deploy(stage_outputs, disable_prompt, terraform_init=False): + with super().deploy( + stage_outputs, + disable_prompt, + terraform_init=False, + force_unlock=force_unlock, + ): env_mapping = {} # DigitalOcean terraform remote state using Spaces Bucket # assumes aws credentials thus we set them to match spaces credentials diff --git a/src/_nebari/subcommands/deploy.py b/src/_nebari/subcommands/deploy.py index fe4cddf1df..b9cdb3571f 100644 --- a/src/_nebari/subcommands/deploy.py +++ b/src/_nebari/subcommands/deploy.py @@ -59,6 +59,11 @@ def deploy( "--skip-remote-state-provision", help="Skip terraform state deployment which is often required in CI once the terraform remote state bootstrapping phase is complete", ), + stage_force_unlock: str = typer.Option( + None, + "--stage-force-unlock", + help="Force unlock the terraform state file for a given stage. This should be only used if you know what you are doing", + ), ): """ Deploy the Nebari cluster from your [purple]nebari-config.yaml[/purple] file. @@ -94,4 +99,5 @@ def deploy( stages, disable_prompt=disable_prompt, disable_checks=disable_checks, + stage_force_unlock=stage_force_unlock, ) diff --git a/src/nebari/hookspecs.py b/src/nebari/hookspecs.py index 7dc6c8e3a4..d24976bfd6 100644 --- a/src/nebari/hookspecs.py +++ b/src/nebari/hookspecs.py @@ -28,7 +28,10 @@ def render(self) -> Dict[str, str]: @contextlib.contextmanager def deploy( - self, stage_outputs: Dict[str, Dict[str, Any]], disable_prompt: bool = False + self, + stage_outputs: Dict[str, Dict[str, Any]], + disable_prompt: bool = False, + force_unlock: str = None, ): yield From aef37c3507dd325b8dc6a1f2f57647d3d1662c20 Mon Sep 17 00:00:00 2001 From: viniciusdc Date: Tue, 5 Nov 2024 11:45:28 -0300 Subject: [PATCH 3/3] add final components for backup-restore deployment --- .../stages/kubernetes_services/__init__.py | 21 ++++ .../template/backup_restore.tf | 34 +++++++ .../services/backup-restore/main.tf | 97 +++++++------------ .../services/backup-restore/variables.tf | 16 ++- 4 files changed, 103 insertions(+), 65 deletions(-) create mode 100644 src/_nebari/stages/kubernetes_services/template/backup_restore.tf diff --git a/src/_nebari/stages/kubernetes_services/__init__.py b/src/_nebari/stages/kubernetes_services/__init__.py index d1e6fd222e..3db643ff1d 100644 --- a/src/_nebari/stages/kubernetes_services/__init__.py +++ b/src/_nebari/stages/kubernetes_services/__init__.py @@ -192,10 +192,14 @@ def check_default(cls, value): class BackupRestoreStorage(schema.Base): type: str + config: Dict[str, Any] = {} class BackupRestore(schema.Base): + enabled: bool = False storage: BackupRestoreStorage = BackupRestoreStorage(type="s3") + image: str = "nebari/nebari-backup-restore" + image_tag: str = "main" class CondaEnvironment(schema.Base): @@ -375,6 +379,7 @@ class InputSchema(schema.Base): jupyterlab: JupyterLab = JupyterLab() jhub_apps: JHubApps = JHubApps() ceph: RookCeph = RookCeph() + backup_restore: BackupRestore = BackupRestore() def _set_storage_type_default_value(self): if self.storage.type is None: @@ -525,6 +530,13 @@ class ArgoWorkflowsInputVars(schema.Base): ) +class BackupRestoreInputVars(schema.Base): + backup_restore_enabled: bool = Field(alias="backup-restore-enabled") + backup_restore_storage: BackupRestoreStorage = Field(alias="backup-restore-storage") + backup_restore_image: str = Field(alias="backup-restore-image") + backup_restore_image_tag: str = Field(alias="backup-restore-image-tag") + + class KubernetesServicesStage(NebariTerraformStage): name = "07-kubernetes-services" priority = 70 @@ -692,6 +704,14 @@ def input_vars(self, stage_outputs: Dict[str, Dict[str, Any]]): keycloak_read_only_user_credentials=keycloak_read_only_user_credentials, ) + backup_restore_vars = BackupRestoreInputVars( + backup_restore_enabled=self.config.backup_restore.enabled, + backup_restore_storage=self.config.backup_restore.storage, + backup_restore_services=self.config.backup_restore.services, + backup_restore_image=self.config.backup_restore.image, + backup_restore_image_tag=self.config.backup_restore.image_tag, + ) + return { **kubernetes_services_vars.model_dump(by_alias=True), **rook_ceph_vars.model_dump(by_alias=True), @@ -701,6 +721,7 @@ def input_vars(self, stage_outputs: Dict[str, Dict[str, Any]]): **monitoring_vars.model_dump(by_alias=True), **argo_workflows_vars.model_dump(by_alias=True), **telemetry_vars.model_dump(by_alias=True), + **backup_restore_vars.model_dump(by_alias=True), } def check( diff --git a/src/_nebari/stages/kubernetes_services/template/backup_restore.tf b/src/_nebari/stages/kubernetes_services/template/backup_restore.tf new file mode 100644 index 0000000000..3b80615bf8 --- /dev/null +++ b/src/_nebari/stages/kubernetes_services/template/backup_restore.tf @@ -0,0 +1,34 @@ +variable "backup-restore-enabled" { + description = "Enable backup-restore service" + type = bool + default = false +} + +variable "backup-restore-storage" { + description = "Storage backend for backup-restore" + type = map(string) + default = {} +} + +variable "backup-restore-image" { + description = "The image to use for the backup-restore service" + type = string +} + +variable "backup-restore-image-tag" { + description = "The tag of the image to use for the backup-restore service" + type = string +} + +module "nebari-backup-restore" { + count = var.backup-restore-enabled ? 1 : 0 + source = "./modules/kubernetes/services/backup-restore" + + external-url = var.endpoint + realm_id = "nebari" + image = var.backup-restore-image + storage = var.backup-restore-storage + image_tag = var.backup-restore-image-tag + namespace = var.environment + clients = ["nebari-cli"] +} diff --git a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/main.tf b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/main.tf index b3333a9a51..1f9b3a11f1 100644 --- a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/main.tf +++ b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/main.tf @@ -1,7 +1,31 @@ +module "jupyterhub-openid-client" { + source = "../keycloak-client" + + realm_id = var.realm_id + client_id = "nebari-cli" + external-url = var.external-url + role_mapping = {} + client_roles = [] + callback-url-paths = [] + service-accounts-enabled = true + service-account-roles = ["realm-admin"] +} + locals { clients = { for client in var.clients : client => client } + services = { + "keycloak.json" = jsonencode({ + "auth" : { + "auth_url" : "https://${var.external-url}/auth", + "realm" : var.realm_id, + "client_id" : "nebari-cli", + "client_secret" : module.jupyterhub-openid-client.client_secret, + "verify_ssl" : false + } + }) + } } resource "random_password" "backup_restore_service_token" { @@ -22,6 +46,17 @@ resource "kubernetes_secret" "backup_restore_service_token" { } } +resource "kubernetes_config_map" "backup-restore-etc" { + metadata { + name = "backup-restore-etc" + namespace = var.namespace + } + + # Merge local.services with the storage.json entry + data = merge(local.services, { + "storage.json" = jsonencode(var.storage) + }) +} resource "kubernetes_service" "backup_restore" { metadata { @@ -41,18 +76,6 @@ resource "kubernetes_service" "backup_restore" { } } -resource "kubernetes_config_map" "backup-restore-etc" { - metadata { - name = "backup-restore-etc" - namespace = var.namespace - } - - data = { - "keycloak.json" = jsonencode({}) - "storage.json" = jsonencode({}) - } -} - resource "kubernetes_service_account" "backup_restore" { metadata { name = "backup-restore" @@ -60,54 +83,6 @@ resource "kubernetes_service_account" "backup_restore" { } } -resource "kubernetes_manifest" "backup_restore" { - manifest = { - apiVersion = "traefik.containo.us/v1alpha1" - kind = "IngressRoute" - metadata = { - name = "backup-restore" - namespace = var.namespace - } - spec = { - entryPoints = ["websecure"] - routes = [ - { - kind = "Rule" - match = "Host(`${var.external-url}`) && PathPrefix(`/backup-restore/`)" - - middlewares = [ - { - name = "nebari-backup-restore-api" - namespace = var.namespace - } - ] - - services = [ - { - name = kubernetes_service.backup_restore.metadata.0.name - port = 8000 - } - ] - } - ] - } - } -} - - -module "jupyterhub-openid-client" { - source = "../keycloak-client" - - realm_id = var.realm_id - client_id = "nebari-cli" - external-url = var.external-url - role_mapping = {} - client_roles = [] - callback-url-paths = [] - service-accounts-enabled = true - service-account-roles = ["realm-admin"] -} - resource "kubernetes_deployment" "backup_restore" { metadata { name = "backup-restore" @@ -135,7 +110,7 @@ resource "kubernetes_deployment" "backup_restore" { container { name = "backup-restore" - image = "${var.backup-restore-image}:${var.backup-restore-image-tag}" + image = "${var.image}:${var.image_tag}" image_pull_policy = "Always" env { diff --git a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/variables.tf b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/variables.tf index 10c6e651ed..c5c9e4c0a8 100644 --- a/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/variables.tf +++ b/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/backup-restore/variables.tf @@ -24,12 +24,20 @@ variable "realm_id" { type = string } -variable "backup-restore-image" { - description = "Backup-restore image" +variable "storage" { + description = "Storage configuration for backup-restore server" + type = object({ + type = string + config = map(string) + }) +} + +variable "image" { + description = "The image to use for the backup-restore service" type = string } -variable "backup-restore-image-tag" { - description = "Version of backup-restore to use" +variable "image_tag" { + description = "The tag of the image to use for the backup-restore service" type = string }