diff --git a/config.tfvars b/config.tfvars index c3028af3..7df97eb5 100644 --- a/config.tfvars +++ b/config.tfvars @@ -407,9 +407,18 @@ confluence_collaborative_editing_enabled = true #confluence_s3_attachments_storage = true # Enable OpenSearch as Confluence search engine and configure resource requests and limits +# It is only supported from Confluence 8.9.0. See: https://confluence.atlassian.com/doc/configuring-opensearch-for-confluence-1387594125.html # confluence_opensearch_enabled = false # confluence_opensearch_requests_cpu = "" # confluence_opensearch_requests_memory = "" +# confluence_opensearch_persistence_size = "" +# confluence_opensearch_initial_admin_password = "" + +# OpenSearch restore configuration +# To restore OpenSearch dataset, you can provide EBS snapshot ID of the OpenSearch volume. +# This volume will be used to pre-create OpenSearch PVC and PV. +# Make sure the snapshot is available in the region you are deploying to and follows all product requirements. +# confluence_opensearch_snapshot_id = "" ################################################################################ # Bitbucket Settings diff --git a/dc-infrastructure.tf b/dc-infrastructure.tf index 1d91539a..5bc7d89b 100644 --- a/dc-infrastructure.tf +++ b/dc-infrastructure.tf @@ -246,6 +246,9 @@ module "confluence" { opensearch_enabled = var.confluence_opensearch_enabled opensearch_requests_cpu = var.confluence_opensearch_requests_cpu opensearch_requests_memory = var.confluence_opensearch_requests_memory + opensearch_snapshot_id = var.confluence_opensearch_snapshot_id + opensearch_persistence_size = var.confluence_opensearch_persistence_size + opensearch_initial_admin_password = var.confluence_opensearch_initial_admin_password } module "bitbucket" { diff --git a/docs/docs/userguide/configuration/CONFLUENCE_CONFIGURATION.md b/docs/docs/userguide/configuration/CONFLUENCE_CONFIGURATION.md index e00ee4e6..5581fb58 100644 --- a/docs/docs/userguide/configuration/CONFLUENCE_CONFIGURATION.md +++ b/docs/docs/userguide/configuration/CONFLUENCE_CONFIGURATION.md @@ -244,6 +244,9 @@ confluence_shared_home_snapshot_id = "" ??? Warning "Snapshot and your environment must be in same region" ## Search engine configuration +Starting from Confluence 8.9.0, OpenSearch is supported as search engine. +See: https://confluence.atlassian.com/doc/configuring-opensearch-for-confluence-1387594125.html + ### OpenSearch `confluence_opensearch_enabled` decides whether to use OpenSearch as Confluence search engine. If set to true, a single-node OpenSearch will be created as part of the deployment, and Confluence will be configured to connect to this instance. @@ -258,4 +261,29 @@ The following variables set number of CPU and amount of memory of OpenSearch ins ```terraform confluence_opensearch_requests_cpu = "1" confluence_opensearch_requests_memory = "1Gi" -``` \ No newline at end of file +``` + +### OpenSearch persistent volume size +`confluence_opensearch_persistence_size` sets the size of OpenSearch's persistence volume. (Used default values as example.) + +```terraform +confluence_opensearch_persistence_size = "10Gi" +``` + +### OpenSearch initial admin password +From OpenSearch Helm chart version 2.18.0 and App Version OpenSearch 2.12.0 onwards a custom strong password needs to be provided in order to setup demo admin user. +If no password is specified, a random password will be generated. + +```terraform +confluence_opensearch_initial_admin_password = "OpenSearchAtl123!" +``` + +### OpenSearch restore configuration +To restore OpenSearch dataset, you can provide EBS snapshot ID of the OpenSearch volume. This volume will be used to pre-create OpenSearch PVC and PV. + +`confluence_opensearch_snapshot_id` sets the id of OpenSearch EBS snapshot. +Make sure the snapshot is available in the region you are deploying to and follows all product requirements. + +```terraform +confluence_opensearch_snapshot_id = "" +``` diff --git a/modules/products/confluence/helm.tf b/modules/products/confluence/helm.tf index 9ced00a8..4104696e 100644 --- a/modules/products/confluence/helm.tf +++ b/modules/products/confluence/helm.tf @@ -5,7 +5,8 @@ resource "helm_release" "confluence" { depends_on = [ kubernetes_job.pre_install, kubernetes_persistent_volume_claim.local_home, - time_sleep.wait_confluence_termination + time_sleep.wait_confluence_termination, + kubernetes_persistent_volume_claim.opensearch, ] name = local.product_name namespace = var.namespace diff --git a/modules/products/confluence/locals.tf b/modules/products/confluence/locals.tf index 6164a1a7..a6d782bf 100644 --- a/modules/products/confluence/locals.tf +++ b/modules/products/confluence/locals.tf @@ -79,6 +79,15 @@ locals { memory = var.opensearch_requests_memory } } + persistence = { + size = var.opensearch_persistence_size + } + credentials = { + createSecret = false + existingSecretRef = { + name = "opensearch-initial-password" + } + } } }) : yamlencode({}) @@ -121,9 +130,11 @@ locals { # DC App Performance Toolkit analytics dcapt_analytics_property = ["-Dcom.atlassian.dcapt.deployment=terraform"] - irsa_properties = var.confluence_s3_attachments_storage ? ["-Daws.webIdentityTokenFile=/var/run/secrets/eks.amazonaws.com/serviceaccount/token", + irsa_properties = var.confluence_s3_attachments_storage ? [ + "-Daws.webIdentityTokenFile=/var/run/secrets/eks.amazonaws.com/serviceaccount/token", "-Dconfluence.filestore.attachments.s3.bucket.name=${var.eks.confluence_s3_bucket_name}", - "-Dconfluence.filestore.attachments.s3.bucket.region=${var.region_name}"] : [] + "-Dconfluence.filestore.attachments.s3.bucket.region=${var.region_name}" + ] : [] service_account_annotations = var.confluence_s3_attachments_storage ? yamlencode({ serviceAccount = { @@ -133,5 +144,6 @@ locals { } }) : yamlencode({}) - storage_class = "gp2" + storage_class = "gp2" + opensearch_storage_class = "gp2" } diff --git a/modules/products/confluence/opensearch_storage.tf b/modules/products/confluence/opensearch_storage.tf new file mode 100644 index 00000000..6a89f56c --- /dev/null +++ b/modules/products/confluence/opensearch_storage.tf @@ -0,0 +1,61 @@ +data "aws_ebs_snapshot" "opensearch_snapshot" { + count = var.opensearch_enabled && var.opensearch_snapshot_id != null ? 1 : 0 + + snapshot_ids = [var.opensearch_snapshot_id] + most_recent = true +} + +resource "aws_ebs_volume" "opensearch" { + count = var.opensearch_enabled && var.opensearch_snapshot_id != null ? 1 : 0 + + availability_zone = var.eks.availability_zone + snapshot_id = var.opensearch_snapshot_id + size = data.aws_ebs_snapshot.opensearch_snapshot[0].volume_size + type = local.opensearch_storage_class + tags = { + Name = "confluence-opensearch" + } +} + +resource "kubernetes_persistent_volume" "opensearch" { + count = var.opensearch_enabled && var.opensearch_snapshot_id != null ? 1 : 0 + + metadata { + name = "confluence-opensearch-pv" + } + spec { + access_modes = ["ReadWriteOnce"] + capacity = { + storage = data.aws_ebs_snapshot.opensearch_snapshot[0].volume_size + } + storage_class_name = local.opensearch_storage_class + persistent_volume_source { + aws_elastic_block_store { + volume_id = aws_ebs_volume.opensearch[0].id + } + } + claim_ref { + name = "opensearch-cluster-master-opensearch-cluster-master-0" + namespace = var.namespace + } + } +} + +resource "kubernetes_persistent_volume_claim" "opensearch" { + count = var.opensearch_enabled && var.opensearch_snapshot_id != null ? 1 : 0 + + metadata { + name = "opensearch-cluster-master-opensearch-cluster-master-0" + namespace = var.namespace + } + spec { + access_modes = ["ReadWriteOnce"] + resources { + requests = { + storage = data.aws_ebs_snapshot.opensearch_snapshot[0].volume_size + } + } + storage_class_name = local.opensearch_storage_class + volume_name = kubernetes_persistent_volume.opensearch[0].metadata.0.name + } +} diff --git a/modules/products/confluence/provider_version.tf b/modules/products/confluence/provider_version.tf index 15ed9e10..dcd1848a 100644 --- a/modules/products/confluence/provider_version.tf +++ b/modules/products/confluence/provider_version.tf @@ -9,5 +9,9 @@ terraform { helm = { version = "~> 2.4" } + random = { + source = "hashicorp/random" + version = "3.6.1" + } } } \ No newline at end of file diff --git a/modules/products/confluence/secrets.tf b/modules/products/confluence/secrets.tf index fef1aefb..b3b3f140 100644 --- a/modules/products/confluence/secrets.tf +++ b/modules/products/confluence/secrets.tf @@ -26,3 +26,24 @@ resource "kubernetes_secret" "license_secret" { license-key = var.confluence_configuration["license"] } } + +################################################################################ +# Kubernetes secret to store OpenSearch initial password +################################################################################ +resource "kubernetes_secret" "opensearch_secret" { + count = var.opensearch_enabled ? 1 : 0 + + metadata { + name = "opensearch-initial-password" + namespace = var.namespace + } + + data = { + OPENSEARCH_INITIAL_ADMIN_PASSWORD = var.opensearch_initial_admin_password != null ? var.opensearch_initial_admin_password : random_password.opensearch.result + } +} + +resource "random_password" "opensearch" { + length = 16 + special = true +} \ No newline at end of file diff --git a/modules/products/confluence/variables.tf b/modules/products/confluence/variables.tf index 3dc511f6..b6e9f8da 100644 --- a/modules/products/confluence/variables.tf +++ b/modules/products/confluence/variables.tf @@ -193,4 +193,22 @@ variable "opensearch_requests_memory" { description = "The minimum amount of memory to allocate to the OpenSearch instance" type = string default = "1Gi" +} + +variable "opensearch_persistence_size" { + description = "OpenSearch persistent volume size" + type = string + default = "10Gi" +} + +variable "opensearch_initial_admin_password" { + description = "OpenSearch initial admin password" + type = string + default = null +} + +variable "opensearch_snapshot_id" { + description = "EBS Snapshot ID with OpenSearch data." + type = string + default = null } \ No newline at end of file diff --git a/provider_version.tf b/provider_version.tf index 5470c2fe..39b9d310 100644 --- a/provider_version.tf +++ b/provider_version.tf @@ -9,5 +9,9 @@ terraform { helm = { version = "~> 2.4" } + random = { + source = "hashicorp/random" + version = "3.6.1" + } } } diff --git a/providers.tf b/providers.tf index 1e6228d6..539a4232 100644 --- a/providers.tf +++ b/providers.tf @@ -28,4 +28,8 @@ provider "helm" { command = "aws" } } +} + +provider "random" { + # Configuration options } \ No newline at end of file diff --git a/test/unittest/confluence_test.go b/test/unittest/confluence_test.go index 906e2249..038d8249 100644 --- a/test/unittest/confluence_test.go +++ b/test/unittest/confluence_test.go @@ -16,6 +16,8 @@ func TestConfluenceVariablesPopulatedWithValidValues(t *testing.T) { tfOptions := GenerateTFOptions(ConfluenceCorrectVariables, t, confluenceModule) plan := terraform.InitAndPlanAndShowWithStruct(t, tfOptions) + terraform.RequirePlannedValuesMapKeyExists(t, plan, "random_password.opensearch") + confluenceKey := "helm_release.confluence" terraform.RequirePlannedValuesMapKeyExists(t, plan, confluenceKey) confluence := plan.ResourcePlannedValuesMap[confluenceKey] @@ -24,7 +26,7 @@ func TestConfluenceVariablesPopulatedWithValidValues(t *testing.T) { assert.Equal(t, float64(testTimeout*60), confluence.AttributeValues["timeout"]) assert.Equal(t, "https://atlassian.github.io/data-center-helm-charts", confluence.AttributeValues["repository"]) openSearchValues := confluence.AttributeValues["values"].([]interface{})[9].(string) - assert.Equal(t, "\"opensearch\":\n \"enabled\": true\n \"resources\":\n \"requests\":\n \"cpu\": \"2\"\n \"memory\": \"2Gi\"\n", openSearchValues) + assert.Equal(t, "\"opensearch\":\n \"credentials\":\n \"createSecret\": false\n \"existingSecretRef\":\n \"name\": \"opensearch-initial-password\"\n \"enabled\": true\n \"persistence\":\n \"size\": \"10Gi\"\n \"resources\":\n \"requests\":\n \"cpu\": \"2\"\n \"memory\": \"2Gi\"\n", openSearchValues) } func TestConfluenceVariablesPopulatedWithInvalidValues(t *testing.T) { @@ -114,11 +116,12 @@ var ConfluenceCorrectVariables = map[string]interface{}{ "max_heap": "1024m", "stack_size": "1024k", }, - "enable_synchrony": false, - "db_snapshot_build_number": "1234", - "termination_grace_period": 0, - "additional_jvm_args": []string{}, - "opensearch_enabled": true, - "opensearch_requests_cpu": "2", - "opensearch_requests_memory": "2Gi", + "enable_synchrony": false, + "db_snapshot_build_number": "1234", + "termination_grace_period": 0, + "additional_jvm_args": []string{}, + "opensearch_enabled": true, + "opensearch_requests_cpu": "2", + "opensearch_requests_memory": "2Gi", + "opensearch_persistence_size": "10Gi", } diff --git a/variables.tf b/variables.tf index f3ebf8fe..098256cd 100644 --- a/variables.tf +++ b/variables.tf @@ -752,6 +752,32 @@ variable "confluence_opensearch_requests_memory" { default = "1Gi" } +variable "confluence_opensearch_persistence_size" { + description = "OpenSearch persistent volume size" + type = string + default = "10Gi" +} + +variable "confluence_opensearch_initial_admin_password" { + description = "Initial admin password for the Confluence OpenSearch instance." + type = string + default = null + validation { + condition = can(regex("^([aA-zZ]|[0-9]|[!@#$% &*()-_=+[]{}<>:?]).{12,}$", var.confluence_opensearch_initial_admin_password)) || var.confluence_opensearch_initial_admin_password == null + error_message = "Confluence OpenSearch initial password must be at least 12 characters long and contain combination of numbers, letters, and special characters." + } +} + +variable "confluence_opensearch_snapshot_id" { + description = "EBS Snapshot ID with OpenSearch data." + type = string + default = null + validation { + condition = var.confluence_opensearch_snapshot_id == null || can(regex("^snap-\\w{17}$", var.confluence_opensearch_snapshot_id)) + error_message = "Provide correct EBS snapshot ID." + } +} + ################################################################################ # Bitbucket Variables ################################################################################