From 4669d7bde8e4d586f7644579f904c3db528c1082 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Thu, 14 May 2026 12:55:44 +0200 Subject: [PATCH 01/21] feat: COS-Dev mesh --- terraform/cos-dev/applications.tf | 268 ++++++++++-------- terraform/cos-dev/integrations.tf | 133 ++++++++- terraform/cos-dev/offers.tf | 104 +++++++ terraform/cos-dev/outputs.tf | 16 ++ .../tests/conditional_ingress.tftest.hcl | 116 ++++++++ .../cos-dev/tests/mesh_enabled.tftest.hcl | 65 +++++ terraform/cos-dev/variables.tf | 38 +++ 7 files changed, 615 insertions(+), 125 deletions(-) create mode 100644 terraform/cos-dev/tests/mesh_enabled.tftest.hcl diff --git a/terraform/cos-dev/applications.tf b/terraform/cos-dev/applications.tf index b76149c0..24518092 100644 --- a/terraform/cos-dev/applications.tf +++ b/terraform/cos-dev/applications.tf @@ -35,6 +35,32 @@ module "grafana" { replace_triggers = [terraform_data.grafana_litestream_resource.id] } +module "istio-ingress" { + count = var.mesh_enabled ? 1 : 0 + source = "git::https://github.com/canonical/istio-ingress-k8s-operator//terraform" + app_name = var.catalogue.app_name + channel = local.channels.catalogue + config = var.catalogue.config + constraints = var.catalogue.constraints + model_uuid = var.model_uuid + revision = local.revisions.catalogue + storage_directives = var.catalogue.storage_directives + units = var.catalogue.units +} + +module "istio-beacon" { + count = var.mesh_enabled ? 1 : 0 + source = "git::https://github.com/canonical/istio-beacon-k8s-operator//terraform" + app_name = var.catalogue.app_name + channel = local.channels.catalogue + config = var.catalogue.config + constraints = var.catalogue.constraints + model_uuid = var.model_uuid + revision = local.revisions.catalogue + storage_directives = var.catalogue.storage_directives + units = var.catalogue.units +} + module "loki_coordinator" { source = "git::https://github.com/canonical/loki-operators//coordinator/terraform" app_name = var.loki_coordinator.app_name @@ -195,8 +221,6 @@ module "opentelemetry_collector" { units = var.opentelemetry_collector.units } -# -------------- # SeaweedFS (storage_backend = "seaweedfs") -------------- - module "seaweedfs" { count = var.storage_backend == "seaweedfs" ? 1 : 0 source = "git::https://github.com/canonical/observability-stack//terraform/seaweedfs" @@ -210,126 +234,6 @@ module "seaweedfs" { units = var.seaweedfs.units } -# -------------- # S3-integrators (storage_backend = "s3") -------------- - -resource "juju_secret" "loki_s3_credentials" { - count = var.storage_backend == "s3" ? 1 : 0 - model_uuid = var.model_uuid - name = "loki-s3-credentials" - value = { - access-key = var.s3_access_key - secret-key = var.s3_secret_key - } - info = "S3 credentials for Loki" -} - -resource "juju_access_secret" "loki_s3_credentials_access" { - count = var.storage_backend == "s3" ? 1 : 0 - model_uuid = var.model_uuid - applications = [juju_application.s3_integrator_loki[0].name] - secret_id = juju_secret.loki_s3_credentials[0].secret_id -} - -# TODO: Replace with a remote terraform module once the s3-integrator charm exposes one. -resource "juju_application" "s3_integrator_loki" { - count = var.storage_backend == "s3" ? 1 : 0 - config = merge({ - endpoint = var.s3_endpoint - bucket = var.loki_bucket - credentials = "secret:${juju_secret.loki_s3_credentials[0].secret_id}" - }, var.s3_integrator.config) - constraints = var.s3_integrator.constraints - model_uuid = var.model_uuid - name = "${var.loki_coordinator.app_name}-s3-integrator" - storage_directives = var.s3_integrator.storage_directives - trust = true - units = var.s3_integrator.units - - charm { - name = "s3-integrator" - channel = local.channels.s3_integrator - revision = local.revisions.s3_integrator - } -} - -resource "juju_secret" "mimir_s3_credentials" { - count = var.storage_backend == "s3" ? 1 : 0 - model_uuid = var.model_uuid - name = "mimir-s3-credentials" - value = { - access-key = var.s3_access_key - secret-key = var.s3_secret_key - } - info = "S3 credentials for Mimir" -} - -resource "juju_access_secret" "mimir_s3_credentials_access" { - count = var.storage_backend == "s3" ? 1 : 0 - model_uuid = var.model_uuid - applications = [juju_application.s3_integrator_mimir[0].name] - secret_id = juju_secret.mimir_s3_credentials[0].secret_id -} - -resource "juju_application" "s3_integrator_mimir" { - count = var.storage_backend == "s3" ? 1 : 0 - config = merge({ - endpoint = var.s3_endpoint - bucket = var.mimir_bucket - credentials = "secret:${juju_secret.mimir_s3_credentials[0].secret_id}" - }, var.s3_integrator.config) - constraints = var.s3_integrator.constraints - model_uuid = var.model_uuid - name = "${var.mimir_coordinator.app_name}-s3-integrator" - storage_directives = var.s3_integrator.storage_directives - trust = true - units = var.s3_integrator.units - - charm { - name = "s3-integrator" - channel = local.channels.s3_integrator - revision = local.revisions.s3_integrator - } -} - -resource "juju_secret" "tempo_s3_credentials" { - count = var.storage_backend == "s3" ? 1 : 0 - model_uuid = var.model_uuid - name = "tempo-s3-credentials" - value = { - access-key = var.s3_access_key - secret-key = var.s3_secret_key - } - info = "S3 credentials for Tempo" -} - -resource "juju_access_secret" "tempo_s3_credentials_access" { - count = var.storage_backend == "s3" ? 1 : 0 - model_uuid = var.model_uuid - applications = [juju_application.s3_integrator_tempo[0].name] - secret_id = juju_secret.tempo_s3_credentials[0].secret_id -} - -resource "juju_application" "s3_integrator_tempo" { - count = var.storage_backend == "s3" ? 1 : 0 - config = merge({ - endpoint = var.s3_endpoint - bucket = var.tempo_bucket - credentials = "secret:${juju_secret.tempo_s3_credentials[0].secret_id}" - }, var.s3_integrator.config) - constraints = var.s3_integrator.constraints - model_uuid = var.model_uuid - name = "${var.tempo_coordinator.app_name}-s3-integrator" - storage_directives = var.s3_integrator.storage_directives - trust = true - units = var.s3_integrator.units - - charm { - name = "s3-integrator" - channel = local.channels.s3_integrator - revision = local.revisions.s3_integrator - } -} - module "ssc" { count = var.internal_tls ? 1 : 0 source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform" @@ -472,3 +376,123 @@ module "traefik" { storage_directives = var.traefik.storage_directives units = var.traefik.units } + +# -------------- # S3-integrator resources (storage_backend = "s3") -------------- + +resource "juju_secret" "loki_s3_credentials" { + count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid + name = "loki-s3-credentials" + value = { + access-key = var.s3_access_key + secret-key = var.s3_secret_key + } + info = "S3 credentials for Loki" +} + +resource "juju_access_secret" "loki_s3_credentials_access" { + count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid + applications = [juju_application.s3_integrator_loki[0].name] + secret_id = juju_secret.loki_s3_credentials[0].secret_id +} + +# TODO: Replace with a remote terraform module once the s3-integrator charm exposes one. +resource "juju_application" "s3_integrator_loki" { + count = var.storage_backend == "s3" ? 1 : 0 + config = merge({ + endpoint = var.s3_endpoint + bucket = var.loki_bucket + credentials = "secret:${juju_secret.loki_s3_credentials[0].secret_id}" + }, var.s3_integrator.config) + constraints = var.s3_integrator.constraints + model_uuid = var.model_uuid + name = "${var.loki_coordinator.app_name}-s3-integrator" + storage_directives = var.s3_integrator.storage_directives + trust = true + units = var.s3_integrator.units + + charm { + name = "s3-integrator" + channel = local.channels.s3_integrator + revision = local.revisions.s3_integrator + } +} + +resource "juju_secret" "mimir_s3_credentials" { + count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid + name = "mimir-s3-credentials" + value = { + access-key = var.s3_access_key + secret-key = var.s3_secret_key + } + info = "S3 credentials for Mimir" +} + +resource "juju_access_secret" "mimir_s3_credentials_access" { + count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid + applications = [juju_application.s3_integrator_mimir[0].name] + secret_id = juju_secret.mimir_s3_credentials[0].secret_id +} + +resource "juju_application" "s3_integrator_mimir" { + count = var.storage_backend == "s3" ? 1 : 0 + config = merge({ + endpoint = var.s3_endpoint + bucket = var.mimir_bucket + credentials = "secret:${juju_secret.mimir_s3_credentials[0].secret_id}" + }, var.s3_integrator.config) + constraints = var.s3_integrator.constraints + model_uuid = var.model_uuid + name = "${var.mimir_coordinator.app_name}-s3-integrator" + storage_directives = var.s3_integrator.storage_directives + trust = true + units = var.s3_integrator.units + + charm { + name = "s3-integrator" + channel = local.channels.s3_integrator + revision = local.revisions.s3_integrator + } +} + +resource "juju_secret" "tempo_s3_credentials" { + count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid + name = "tempo-s3-credentials" + value = { + access-key = var.s3_access_key + secret-key = var.s3_secret_key + } + info = "S3 credentials for Tempo" +} + +resource "juju_access_secret" "tempo_s3_credentials_access" { + count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid + applications = [juju_application.s3_integrator_tempo[0].name] + secret_id = juju_secret.tempo_s3_credentials[0].secret_id +} + +resource "juju_application" "s3_integrator_tempo" { + count = var.storage_backend == "s3" ? 1 : 0 + config = merge({ + endpoint = var.s3_endpoint + bucket = var.tempo_bucket + credentials = "secret:${juju_secret.tempo_s3_credentials[0].secret_id}" + }, var.s3_integrator.config) + constraints = var.s3_integrator.constraints + model_uuid = var.model_uuid + name = "${var.tempo_coordinator.app_name}-s3-integrator" + storage_directives = var.s3_integrator.storage_directives + trust = true + units = var.s3_integrator.units + + charm { + name = "s3-integrator" + channel = local.channels.s3_integrator + revision = local.revisions.s3_integrator + } +} diff --git a/terraform/cos-dev/integrations.tf b/terraform/cos-dev/integrations.tf index 966ef8a2..9efba9a7 100644 --- a/terraform/cos-dev/integrations.tf +++ b/terraform/cos-dev/integrations.tf @@ -649,7 +649,7 @@ resource "juju_integration" "ingress" { app_name = module.mimir_coordinator.app_name endpoint = "ingress" } - } : k => v if var.ingress[k] + } : k => v if var.ingress[k] && !var.mesh_enabled } model_uuid = var.model_uuid @@ -666,7 +666,7 @@ resource "juju_integration" "ingress" { } resource "juju_integration" "grafana_ingress" { - count = var.ingress.grafana ? 1 : 0 + count = var.ingress.grafana && !var.mesh_enabled ? 1 : 0 model_uuid = var.model_uuid @@ -694,7 +694,7 @@ resource "juju_integration" "traefik_route" { app_name = module.tempo_coordinator.app_name endpoint = module.tempo_coordinator.requires.ingress } - } : k => v if var.ingress[k] + } : k => v if var.ingress[k] && !var.mesh_enabled } model_uuid = var.model_uuid @@ -710,6 +710,86 @@ resource "juju_integration" "traefik_route" { } } +resource "juju_integration" "istio_ingress" { + for_each = { + for k, v in { + alertmanager = { + app_name = module.alertmanager.app_name + endpoint = module.alertmanager.requires.ingress + } + catalogue = { + app_name = module.catalogue.app_name + endpoint = module.catalogue.requires.ingress + } + loki = { + app_name = module.loki_coordinator.app_name + endpoint = "ingress" + } + mimir = { + app_name = module.mimir_coordinator.app_name + endpoint = "ingress" + } + } : k => v if var.ingress[k] && var.mesh_enabled + } + + model_uuid = var.model_uuid + + application { + name = module.istio_ingress[0].app_name + endpoint = module.istio_ingress[0].provides.ingress + } + + application { + name = each.value.app_name + endpoint = each.value.endpoint + } +} + +resource "juju_integration" "grafana_istio_ingress" { + count = var.ingress.grafana && var.mesh_enabled ? 1 : 0 + + model_uuid = var.model_uuid + + application { + name = module.grafana.app_name + endpoint = module.grafana.requires.ingress + } + + application { + name = module.istio_ingress[0].app_name + endpoint = module.istio_ingress[0].provides.ingress + } + + lifecycle { replace_triggered_by = [terraform_data.grafana_ingress_interface, terraform_data.grafana_litestream_resource] } +} + +resource "juju_integration" "istio_ingress_route" { + for_each = { + for k, v in { + opentelemetry_collector = { + app_name = module.opentelemetry_collector.app_name + endpoint = module.opentelemetry_collector.requires.ingress + } + tempo = { + app_name = module.tempo_coordinator.app_name + endpoint = module.tempo_coordinator.requires.ingress + } + } : k => v if var.ingress[k] && var.mesh_enabled + } + + model_uuid = var.model_uuid + + application { + name = module.istio_ingress[0].app_name + endpoint = module.istio_ingress[0].provides["istio-ingress-route"] + } + + application { + name = each.value.app_name + endpoint = each.value.endpoint + } +} + # -------------- # Provided by OpenTelemetry Collector -------------- resource "juju_integration" "opentelemetry_collector_mimir_metrics" { @@ -862,3 +942,50 @@ resource "juju_integration" "traces_and_metrics_correlation" { } } +# -------------- # Service Mesh --------------------- + +resource "juju_integration" "istio_beacon" { + for_each = var.mesh_enabled ? { + alertmanager = { + app_name = module.alertmanager.app_name + endpoint = module.alertmanager.requires.service_mesh + } + catalogue = { + app_name = module.catalogue.app_name + endpoint = module.catalogue.requires.service_mesh + } + grafana = { + app_name = module.grafana.app_name + endpoint = module.grafana.requires.service_mesh + } + loki = { + app_name = module.loki_coordinator.app_name + endpoint = module.loki_coordinator.requires.service_mesh + } + mimir = { + app_name = module.mimir_coordinator.app_name + endpoint = module.mimir_coordinator.requires.service_mesh + } + opentelemetry_collector = { + app_name = module.opentelemetry_collector.app_name + endpoint = module.opentelemetry_collector.requires.service_mesh + } + tempo = { + app_name = module.tempo_coordinator.app_name + endpoint = module.tempo_coordinator.requires.service_mesh + } + } : {} + + model_uuid = var.model_uuid + + application { + name = module.istio_beacon[0].app_name + endpoint = module.istio_beacon[0].provides.service_mesh + } + + application { + name = each.value.app_name + endpoint = each.value.endpoint + } +} + diff --git a/terraform/cos-dev/offers.tf b/terraform/cos-dev/offers.tf index a5937c88..37e047c1 100644 --- a/terraform/cos-dev/offers.tf +++ b/terraform/cos-dev/offers.tf @@ -34,3 +34,107 @@ resource "juju_offer" "tempo_tracing" { application_name = module.tempo_coordinator.app_name endpoints = [module.tempo_coordinator.provides.tracing] } + +# -------------- # CMR Mesh offers -------------- # + +resource "juju_offer" "alertmanager_provide_cmr_mesh" { + name = "alertmanager-provide-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.alertmanager.app_name + endpoints = [module.alertmanager.provides.provide_cmr_mesh] +} + +resource "juju_offer" "alertmanager_require_cmr_mesh" { + name = "alertmanager-require-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.alertmanager.app_name + endpoints = [module.alertmanager.requires.require_cmr_mesh] +} + +resource "juju_offer" "catalogue_provide_cmr_mesh" { + name = "catalogue-provide-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.catalogue.app_name + endpoints = [module.catalogue.provides.provide_cmr_mesh] +} + +resource "juju_offer" "catalogue_require_cmr_mesh" { + name = "catalogue-require-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.catalogue.app_name + endpoints = [module.catalogue.requires.require_cmr_mesh] +} + +resource "juju_offer" "grafana_provide_cmr_mesh" { + name = "grafana-provide-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.grafana.app_name + endpoints = [module.grafana.provides.provide_cmr_mesh] + + lifecycle { replace_triggered_by = [terraform_data.grafana_litestream_resource] } +} + +resource "juju_offer" "grafana_require_cmr_mesh" { + name = "grafana-require-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.grafana.app_name + endpoints = [module.grafana.requires.require_cmr_mesh] + + lifecycle { replace_triggered_by = [terraform_data.grafana_litestream_resource] } +} + +resource "juju_offer" "loki_provide_cmr_mesh" { + name = "loki-provide-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.loki_coordinator.app_name + endpoints = [module.loki_coordinator.provides.provide_cmr_mesh] +} + +resource "juju_offer" "loki_require_cmr_mesh" { + name = "loki-require-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.loki_coordinator.app_name + endpoints = [module.loki_coordinator.requires.require_cmr_mesh] +} + +resource "juju_offer" "mimir_provide_cmr_mesh" { + name = "mimir-provide-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.mimir_coordinator.app_name + endpoints = [module.mimir_coordinator.provides.provide_cmr_mesh] +} + +resource "juju_offer" "mimir_require_cmr_mesh" { + name = "mimir-require-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.mimir_coordinator.app_name + endpoints = [module.mimir_coordinator.requires.require_cmr_mesh] +} + +resource "juju_offer" "opentelemetry_collector_provide_cmr_mesh" { + name = "opentelemetry-collector-provide-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.opentelemetry_collector.app_name + endpoints = [module.opentelemetry_collector.provides.provide_cmr_mesh] +} + +resource "juju_offer" "opentelemetry_collector_require_cmr_mesh" { + name = "opentelemetry-collector-require-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.opentelemetry_collector.app_name + endpoints = [module.opentelemetry_collector.requires.require_cmr_mesh] +} + +resource "juju_offer" "tempo_provide_cmr_mesh" { + name = "tempo-provide-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.tempo_coordinator.app_name + endpoints = [module.tempo_coordinator.provides.provide_cmr_mesh] +} + +resource "juju_offer" "tempo_require_cmr_mesh" { + name = "tempo-require-cmr-mesh" + model_uuid = var.model_uuid + application_name = module.tempo_coordinator.app_name + endpoints = [module.tempo_coordinator.requires.require_cmr_mesh] +} diff --git a/terraform/cos-dev/outputs.tf b/terraform/cos-dev/outputs.tf index a9cfbd75..74d663ba 100644 --- a/terraform/cos-dev/outputs.tf +++ b/terraform/cos-dev/outputs.tf @@ -7,6 +7,22 @@ output "offers" { loki_logging = juju_offer.loki_logging mimir_receive_remote_write = juju_offer.mimir_receive_remote_write tempo_tracing = juju_offer.tempo_tracing + + # CMR Mesh + alertmanager_provide_cmr_mesh = juju_offer.alertmanager_provide_cmr_mesh + alertmanager_require_cmr_mesh = juju_offer.alertmanager_require_cmr_mesh + catalogue_provide_cmr_mesh = juju_offer.catalogue_provide_cmr_mesh + catalogue_require_cmr_mesh = juju_offer.catalogue_require_cmr_mesh + grafana_provide_cmr_mesh = juju_offer.grafana_provide_cmr_mesh + grafana_require_cmr_mesh = juju_offer.grafana_require_cmr_mesh + loki_provide_cmr_mesh = juju_offer.loki_provide_cmr_mesh + loki_require_cmr_mesh = juju_offer.loki_require_cmr_mesh + mimir_provide_cmr_mesh = juju_offer.mimir_provide_cmr_mesh + mimir_require_cmr_mesh = juju_offer.mimir_require_cmr_mesh + opentelemetry_collector_provide_cmr_mesh = juju_offer.opentelemetry_collector_provide_cmr_mesh + opentelemetry_collector_require_cmr_mesh = juju_offer.opentelemetry_collector_require_cmr_mesh + tempo_provide_cmr_mesh = juju_offer.tempo_provide_cmr_mesh + tempo_require_cmr_mesh = juju_offer.tempo_require_cmr_mesh } description = "All Juju offers which are exposed by this product module" } diff --git a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl index bb4f421a..bc9f5762 100644 --- a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl +++ b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl @@ -162,3 +162,119 @@ run "ingress_partial_override" { error_message = "Expected traefik_route to contain 'opentelemetry_collector' key" } } + +# --- mesh_enabled: all ingress via istio, none via traefik --- + +run "mesh_ingress_all_enabled" { + command = plan + + variables { + mesh_enabled = true + internal_tls = false + } + + # Traefik ingress resources should be empty + assert { + condition = length(juju_integration.ingress) == 0 + error_message = "Expected 0 traefik ingress integrations when mesh is enabled, got ${length(juju_integration.ingress)}" + } + + assert { + condition = length(juju_integration.grafana_ingress) == 0 + error_message = "Expected 0 grafana_ingress integrations when mesh is enabled, got ${length(juju_integration.grafana_ingress)}" + } + + assert { + condition = length(juju_integration.traefik_route) == 0 + error_message = "Expected 0 traefik_route integrations when mesh is enabled, got ${length(juju_integration.traefik_route)}" + } + + # Istio ingress resources should be populated + assert { + condition = length(juju_integration.istio_ingress) == 4 + error_message = "Expected 4 istio_ingress integrations (alertmanager, catalogue, loki, mimir), got ${length(juju_integration.istio_ingress)}" + } + + assert { + condition = length(juju_integration.grafana_istio_ingress) == 1 + error_message = "Expected 1 grafana_istio_ingress integration, got ${length(juju_integration.grafana_istio_ingress)}" + } + + assert { + condition = length(juju_integration.istio_ingress_route) == 2 + error_message = "Expected 2 istio_ingress_route integrations (opentelemetry_collector, tempo), got ${length(juju_integration.istio_ingress_route)}" + } +} + +# --- mesh_enabled with partial ingress override --- + +run "mesh_ingress_partial" { + command = plan + + variables { + mesh_enabled = true + internal_tls = false + ingress = { + alertmanager = false + catalogue = true + grafana = false + loki = true + mimir = false + opentelemetry_collector = true + tempo = false + } + } + + # Traefik resources should all be empty + assert { + condition = length(juju_integration.ingress) == 0 + error_message = "Expected 0 traefik ingress integrations when mesh is enabled, got ${length(juju_integration.ingress)}" + } + + assert { + condition = length(juju_integration.grafana_ingress) == 0 + error_message = "Expected 0 grafana_ingress integrations when mesh is enabled" + } + + assert { + condition = length(juju_integration.traefik_route) == 0 + error_message = "Expected 0 traefik_route integrations when mesh is enabled" + } + + # Istio resources should respect the ingress toggles + assert { + condition = length(juju_integration.istio_ingress) == 2 + error_message = "Expected 2 istio_ingress integrations (catalogue, loki), got ${length(juju_integration.istio_ingress)}" + } + + assert { + condition = length(juju_integration.grafana_istio_ingress) == 0 + error_message = "Expected 0 grafana_istio_ingress integrations when grafana ingress is disabled" + } + + assert { + condition = length(juju_integration.istio_ingress_route) == 1 + error_message = "Expected 1 istio_ingress_route integration (opentelemetry_collector), got ${length(juju_integration.istio_ingress_route)}" + } +} + +# --- mesh disabled (default): no istio ingress resources --- + +run "no_mesh_no_istio_ingress" { + command = plan + + assert { + condition = length(juju_integration.istio_ingress) == 0 + error_message = "Expected 0 istio_ingress integrations when mesh is disabled, got ${length(juju_integration.istio_ingress)}" + } + + assert { + condition = length(juju_integration.grafana_istio_ingress) == 0 + error_message = "Expected 0 grafana_istio_ingress integrations when mesh is disabled" + } + + assert { + condition = length(juju_integration.istio_ingress_route) == 0 + error_message = "Expected 0 istio_ingress_route integrations when mesh is disabled" + } +} diff --git a/terraform/cos-dev/tests/mesh_enabled.tftest.hcl b/terraform/cos-dev/tests/mesh_enabled.tftest.hcl new file mode 100644 index 00000000..54f7448b --- /dev/null +++ b/terraform/cos-dev/tests/mesh_enabled.tftest.hcl @@ -0,0 +1,65 @@ +mock_provider "juju" {} + +variables { + model_uuid = "00000000-0000-0000-0000-000000000000" +} + +# --- mesh_enabled=false (default): no istio modules or integrations deployed --- + +run "mesh_disabled_by_default" { + command = plan + + assert { + condition = length(module.istio-beacon) == 0 + error_message = "Expected no istio-beacon module when mesh is disabled" + } + + assert { + condition = length(module.istio-ingress) == 0 + error_message = "Expected no istio-ingress module when mesh is disabled" + } + + assert { + condition = length(juju_integration.istio_beacon) == 0 + error_message = "Expected no istio_beacon integrations when mesh is disabled" + } +} + +# --- mesh_enabled=true with internal_tls=false: istio modules and integrations deployed --- + +run "mesh_enabled_without_tls" { + command = plan + + variables { + mesh_enabled = true + internal_tls = false + } + + assert { + condition = length(module.istio-beacon) == 1 + error_message = "Expected istio-beacon module to be deployed when mesh is enabled" + } + + assert { + condition = length(module.istio-ingress) == 1 + error_message = "Expected istio-ingress module to be deployed when mesh is enabled" + } + + assert { + condition = length(juju_integration.istio_beacon) == 7 + error_message = "Expected 7 istio_beacon integrations (one per component) when mesh is enabled" + } +} + +# --- mesh_enabled=true with internal_tls=true (default): validation error --- + +run "mesh_enabled_with_internal_tls_fails" { + command = plan + + variables { + mesh_enabled = true + internal_tls = true + } + + expect_failures = [var.mesh_enabled] +} diff --git a/terraform/cos-dev/variables.tf b/terraform/cos-dev/variables.tf index d1d871fb..9c5c6de2 100644 --- a/terraform/cos-dev/variables.tf +++ b/terraform/cos-dev/variables.tf @@ -46,6 +46,18 @@ variable "storage_backend" { } } +# -------------- # Mesh configurations -------------- +variable "mesh_enabled" { + description = "Specify whether to enable the service mesh or not." + type = bool + default = false + + validation { + condition = !(var.mesh_enabled && var.internal_tls) + error_message = "mesh_enabled and internal_tls cannot both be enabled at the same time." + } +} + # -------------- # TLS configurations -------------- variable "internal_tls" { @@ -183,6 +195,32 @@ variable "grafana" { description = "Application configuration for Grafana. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application" } +variable "istio_beacon" { + type = object({ + app_name = optional(string, "istio-beacon") + config = optional(map(string), {}) + constraints = optional(string, "arch=amd64") + revision = optional(number, null) + storage_directives = optional(map(string), {}) + units = optional(number, 1) + }) + default = {} + description = "Application configuration for istio-beacon. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application" +} + +variable "istio_ingress" { + type = object({ + app_name = optional(string, "istio-ingress") + config = optional(map(string), {}) + constraints = optional(string, "arch=amd64") + revision = optional(number, null) + storage_directives = optional(map(string), {}) + units = optional(number, 1) + }) + default = {} + description = "Application configuration for istio-ingress. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application" +} + variable "loki_coordinator" { type = object({ app_name = optional(string, "loki") From 555e7e3468eed26c7802618131432414a6e0f927 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Sun, 17 May 2026 16:16:39 +0200 Subject: [PATCH 02/21] overhaul for mesh --- terraform/cos-dev/applications.tf | 13 +- terraform/cos-dev/integrations.tf | 24 +- terraform/cos-dev/locals.tf | 4 +- terraform/cos-dev/offers.tf | 14 ++ terraform/cos-dev/outputs.tf | 32 +-- .../tests/conditional_ingress.tftest.hcl | 227 +----------------- terraform/cos-dev/tests/mesh.tftest.hcl | 97 ++++++++ .../cos-dev/tests/mesh_enabled.tftest.hcl | 65 ----- .../cos-dev/tests/reverse_proxy.tftest.hcl | 63 +++++ .../tests/topology_and_storage.tftest.hcl | 16 +- terraform/cos-dev/variables.tf | 106 ++++---- 11 files changed, 287 insertions(+), 374 deletions(-) create mode 100644 terraform/cos-dev/tests/mesh.tftest.hcl delete mode 100644 terraform/cos-dev/tests/mesh_enabled.tftest.hcl create mode 100644 terraform/cos-dev/tests/reverse_proxy.tftest.hcl diff --git a/terraform/cos-dev/applications.tf b/terraform/cos-dev/applications.tf index 24518092..c2e8ca59 100644 --- a/terraform/cos-dev/applications.tf +++ b/terraform/cos-dev/applications.tf @@ -36,7 +36,7 @@ module "grafana" { } module "istio-ingress" { - count = var.mesh_enabled ? 1 : 0 + count = var.mesh.enabled ? 1 : 0 source = "git::https://github.com/canonical/istio-ingress-k8s-operator//terraform" app_name = var.catalogue.app_name channel = local.channels.catalogue @@ -49,7 +49,7 @@ module "istio-ingress" { } module "istio-beacon" { - count = var.mesh_enabled ? 1 : 0 + count = var.mesh.enabled ? 1 : 0 source = "git::https://github.com/canonical/istio-beacon-k8s-operator//terraform" app_name = var.catalogue.app_name channel = local.channels.catalogue @@ -235,7 +235,7 @@ module "seaweedfs" { } module "ssc" { - count = var.internal_tls ? 1 : 0 + count = var.reverse_proxy.enabled ? 1 : 0 source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform" app_name = var.ssc.app_name channel = local.channels.ssc @@ -366,6 +366,7 @@ module "tempo_worker_metrics_generator" { } module "traefik" { + count = var.reverse_proxy.enabled ? 1 : 0 source = "git::https://github.com/canonical/traefik-k8s-operator//terraform" app_name = var.traefik.app_name channel = local.channels.traefik @@ -402,7 +403,7 @@ resource "juju_application" "s3_integrator_loki" { count = var.storage_backend == "s3" ? 1 : 0 config = merge({ endpoint = var.s3_endpoint - bucket = var.loki_bucket + bucket = var.s3_buckets.loki credentials = "secret:${juju_secret.loki_s3_credentials[0].secret_id}" }, var.s3_integrator.config) constraints = var.s3_integrator.constraints @@ -441,7 +442,7 @@ resource "juju_application" "s3_integrator_mimir" { count = var.storage_backend == "s3" ? 1 : 0 config = merge({ endpoint = var.s3_endpoint - bucket = var.mimir_bucket + bucket = var.s3_buckets.mimir credentials = "secret:${juju_secret.mimir_s3_credentials[0].secret_id}" }, var.s3_integrator.config) constraints = var.s3_integrator.constraints @@ -480,7 +481,7 @@ resource "juju_application" "s3_integrator_tempo" { count = var.storage_backend == "s3" ? 1 : 0 config = merge({ endpoint = var.s3_endpoint - bucket = var.tempo_bucket + bucket = var.s3_buckets.tempo credentials = "secret:${juju_secret.tempo_s3_credentials[0].secret_id}" }, var.s3_integrator.config) constraints = var.s3_integrator.constraints diff --git a/terraform/cos-dev/integrations.tf b/terraform/cos-dev/integrations.tf index 9efba9a7..82f9b721 100644 --- a/terraform/cos-dev/integrations.tf +++ b/terraform/cos-dev/integrations.tf @@ -649,7 +649,7 @@ resource "juju_integration" "ingress" { app_name = module.mimir_coordinator.app_name endpoint = "ingress" } - } : k => v if var.ingress[k] && !var.mesh_enabled + } : k => v if var.ingress[k] && var.reverse_proxy.enabled } model_uuid = var.model_uuid @@ -666,7 +666,7 @@ resource "juju_integration" "ingress" { } resource "juju_integration" "grafana_ingress" { - count = var.ingress.grafana && !var.mesh_enabled ? 1 : 0 + count = var.ingress.grafana && var.reverse_proxy.enabled ? 1 : 0 model_uuid = var.model_uuid @@ -694,7 +694,7 @@ resource "juju_integration" "traefik_route" { app_name = module.tempo_coordinator.app_name endpoint = module.tempo_coordinator.requires.ingress } - } : k => v if var.ingress[k] && !var.mesh_enabled + } : k => v if var.ingress[k] && var.reverse_proxy.enabled } model_uuid = var.model_uuid @@ -729,7 +729,7 @@ resource "juju_integration" "istio_ingress" { app_name = module.mimir_coordinator.app_name endpoint = "ingress" } - } : k => v if var.ingress[k] && var.mesh_enabled + } : k => v if var.ingress[k] && var.mesh.enabled } model_uuid = var.model_uuid @@ -746,7 +746,7 @@ resource "juju_integration" "istio_ingress" { } resource "juju_integration" "grafana_istio_ingress" { - count = var.ingress.grafana && var.mesh_enabled ? 1 : 0 + count = var.ingress.grafana && var.mesh.enabled ? 1 : 0 model_uuid = var.model_uuid @@ -774,7 +774,7 @@ resource "juju_integration" "istio_ingress_route" { app_name = module.tempo_coordinator.app_name endpoint = module.tempo_coordinator.requires.ingress } - } : k => v if var.ingress[k] && var.mesh_enabled + } : k => v if var.ingress[k] && var.mesh.enabled } model_uuid = var.model_uuid @@ -809,7 +809,7 @@ resource "juju_integration" "opentelemetry_collector_mimir_metrics" { # -------------- # Certificate Integrations -------------- resource "juju_integration" "internal_certificates" { - for_each = var.internal_tls ? { + for_each = var.reverse_proxy.enabled ? { alertmanager = { app_name = module.alertmanager.app_name endpoint = module.alertmanager.requires.certificates @@ -854,7 +854,7 @@ resource "juju_integration" "internal_certificates" { } resource "juju_integration" "traefik_receive_ca_certificate" { - count = var.internal_tls ? 1 : 0 + count = var.reverse_proxy.enabled ? 1 : 0 model_uuid = var.model_uuid application { @@ -871,7 +871,7 @@ resource "juju_integration" "traefik_receive_ca_certificate" { # -------------- # Provided by an external CA -------------- resource "juju_integration" "external_traefik_certificates" { - count = local.tls_termination ? 1 : 0 + count = local.reverse_proxy_termination ? 1 : 0 model_uuid = var.model_uuid application { @@ -885,7 +885,7 @@ resource "juju_integration" "external_traefik_certificates" { } resource "juju_integration" "external_grafana_ca_cert" { - count = local.tls_termination ? 1 : 0 + count = local.reverse_proxy_termination ? 1 : 0 model_uuid = var.model_uuid application { @@ -899,7 +899,7 @@ resource "juju_integration" "external_grafana_ca_cert" { } resource "juju_integration" "external_otelcol_ca_cert" { - count = local.tls_termination ? 1 : 0 + count = local.reverse_proxy_termination ? 1 : 0 model_uuid = var.model_uuid application { @@ -945,7 +945,7 @@ resource "juju_integration" "traces_and_metrics_correlation" { # -------------- # Service Mesh --------------------- resource "juju_integration" "istio_beacon" { - for_each = var.mesh_enabled ? { + for_each = var.mesh.enabled ? { alertmanager = { app_name = module.alertmanager.app_name endpoint = module.alertmanager.requires.service_mesh diff --git a/terraform/cos-dev/locals.tf b/terraform/cos-dev/locals.tf index cd9c490b..b821a6a0 100644 --- a/terraform/cos-dev/locals.tf +++ b/terraform/cos-dev/locals.tf @@ -1,6 +1,6 @@ locals { - tls_termination = var.external_certificates_offer_url != null ? true : false - traefik_base = "ubuntu@20.04" + reverse_proxy_termination = var.reverse_proxy.enabled && var.reverse_proxy.cmr_urls.certificates != null ? true : false + traefik_base = "ubuntu@20.04" tracks = { alertmanager = "dev" catalogue = "dev" diff --git a/terraform/cos-dev/offers.tf b/terraform/cos-dev/offers.tf index 37e047c1..7ea4df9d 100644 --- a/terraform/cos-dev/offers.tf +++ b/terraform/cos-dev/offers.tf @@ -38,6 +38,7 @@ resource "juju_offer" "tempo_tracing" { # -------------- # CMR Mesh offers -------------- # resource "juju_offer" "alertmanager_provide_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "alertmanager-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.alertmanager.app_name @@ -45,6 +46,7 @@ resource "juju_offer" "alertmanager_provide_cmr_mesh" { } resource "juju_offer" "alertmanager_require_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "alertmanager-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.alertmanager.app_name @@ -52,6 +54,7 @@ resource "juju_offer" "alertmanager_require_cmr_mesh" { } resource "juju_offer" "catalogue_provide_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "catalogue-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.catalogue.app_name @@ -59,6 +62,7 @@ resource "juju_offer" "catalogue_provide_cmr_mesh" { } resource "juju_offer" "catalogue_require_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "catalogue-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.catalogue.app_name @@ -66,6 +70,7 @@ resource "juju_offer" "catalogue_require_cmr_mesh" { } resource "juju_offer" "grafana_provide_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "grafana-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.grafana.app_name @@ -75,6 +80,7 @@ resource "juju_offer" "grafana_provide_cmr_mesh" { } resource "juju_offer" "grafana_require_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "grafana-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.grafana.app_name @@ -84,6 +90,7 @@ resource "juju_offer" "grafana_require_cmr_mesh" { } resource "juju_offer" "loki_provide_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "loki-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.loki_coordinator.app_name @@ -91,6 +98,7 @@ resource "juju_offer" "loki_provide_cmr_mesh" { } resource "juju_offer" "loki_require_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "loki-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.loki_coordinator.app_name @@ -98,6 +106,7 @@ resource "juju_offer" "loki_require_cmr_mesh" { } resource "juju_offer" "mimir_provide_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "mimir-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.mimir_coordinator.app_name @@ -105,6 +114,7 @@ resource "juju_offer" "mimir_provide_cmr_mesh" { } resource "juju_offer" "mimir_require_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "mimir-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.mimir_coordinator.app_name @@ -112,6 +122,7 @@ resource "juju_offer" "mimir_require_cmr_mesh" { } resource "juju_offer" "opentelemetry_collector_provide_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "opentelemetry-collector-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.opentelemetry_collector.app_name @@ -119,6 +130,7 @@ resource "juju_offer" "opentelemetry_collector_provide_cmr_mesh" { } resource "juju_offer" "opentelemetry_collector_require_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "opentelemetry-collector-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.opentelemetry_collector.app_name @@ -126,6 +138,7 @@ resource "juju_offer" "opentelemetry_collector_require_cmr_mesh" { } resource "juju_offer" "tempo_provide_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "tempo-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.tempo_coordinator.app_name @@ -133,6 +146,7 @@ resource "juju_offer" "tempo_provide_cmr_mesh" { } resource "juju_offer" "tempo_require_cmr_mesh" { + count = var.mesh.enabled ? 1 : 0 name = "tempo-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.tempo_coordinator.app_name diff --git a/terraform/cos-dev/outputs.tf b/terraform/cos-dev/outputs.tf index 74d663ba..733e3bdc 100644 --- a/terraform/cos-dev/outputs.tf +++ b/terraform/cos-dev/outputs.tf @@ -9,20 +9,20 @@ output "offers" { tempo_tracing = juju_offer.tempo_tracing # CMR Mesh - alertmanager_provide_cmr_mesh = juju_offer.alertmanager_provide_cmr_mesh - alertmanager_require_cmr_mesh = juju_offer.alertmanager_require_cmr_mesh - catalogue_provide_cmr_mesh = juju_offer.catalogue_provide_cmr_mesh - catalogue_require_cmr_mesh = juju_offer.catalogue_require_cmr_mesh - grafana_provide_cmr_mesh = juju_offer.grafana_provide_cmr_mesh - grafana_require_cmr_mesh = juju_offer.grafana_require_cmr_mesh - loki_provide_cmr_mesh = juju_offer.loki_provide_cmr_mesh - loki_require_cmr_mesh = juju_offer.loki_require_cmr_mesh - mimir_provide_cmr_mesh = juju_offer.mimir_provide_cmr_mesh - mimir_require_cmr_mesh = juju_offer.mimir_require_cmr_mesh - opentelemetry_collector_provide_cmr_mesh = juju_offer.opentelemetry_collector_provide_cmr_mesh - opentelemetry_collector_require_cmr_mesh = juju_offer.opentelemetry_collector_require_cmr_mesh - tempo_provide_cmr_mesh = juju_offer.tempo_provide_cmr_mesh - tempo_require_cmr_mesh = juju_offer.tempo_require_cmr_mesh + alertmanager_provide_cmr_mesh = try(juju_offer.alertmanager_provide_cmr_mesh[0], null) + alertmanager_require_cmr_mesh = try(juju_offer.alertmanager_require_cmr_mesh[0], null) + catalogue_provide_cmr_mesh = try(juju_offer.catalogue_provide_cmr_mesh[0], null) + catalogue_require_cmr_mesh = try(juju_offer.catalogue_require_cmr_mesh[0], null) + grafana_provide_cmr_mesh = try(juju_offer.grafana_provide_cmr_mesh[0], null) + grafana_require_cmr_mesh = try(juju_offer.grafana_require_cmr_mesh[0], null) + loki_provide_cmr_mesh = try(juju_offer.loki_provide_cmr_mesh[0], null) + loki_require_cmr_mesh = try(juju_offer.loki_require_cmr_mesh[0], null) + mimir_provide_cmr_mesh = try(juju_offer.mimir_provide_cmr_mesh[0], null) + mimir_require_cmr_mesh = try(juju_offer.mimir_require_cmr_mesh[0], null) + opentelemetry_collector_provide_cmr_mesh = try(juju_offer.opentelemetry_collector_provide_cmr_mesh[0], null) + opentelemetry_collector_require_cmr_mesh = try(juju_offer.opentelemetry_collector_require_cmr_mesh[0], null) + tempo_provide_cmr_mesh = try(juju_offer.tempo_provide_cmr_mesh[0], null) + tempo_require_cmr_mesh = try(juju_offer.tempo_require_cmr_mesh[0], null) } description = "All Juju offers which are exposed by this product module" } @@ -34,6 +34,8 @@ output "components" { alertmanager = module.alertmanager catalogue = module.catalogue grafana = module.grafana + grafana = try(module.istio_beacon[0], null) + grafana = try(module.istio_ingress[0], null) loki_coordinator = module.loki_coordinator loki_worker = try(module.loki_worker[0], null) loki_worker_backend = try(module.loki_worker_backend[0], null) @@ -55,7 +57,7 @@ output "components" { tempo_worker_distributor = try(module.tempo_worker_distributor[0], null) tempo_worker_compactor = try(module.tempo_worker_compactor[0], null) tempo_worker_metrics_generator = try(module.tempo_worker_metrics_generator[0], null) - traefik = module.traefik + traefik = try(module.traefik[0], null) } description = "All Terraform charm modules which make up this product module" } diff --git a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl index bc9f5762..519bc9f0 100644 --- a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl +++ b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl @@ -4,31 +4,13 @@ variables { model_uuid = "00000000-0000-0000-0000-000000000000" } -# --- Default: all ingress integrations enabled --- +# TODO: This feature also depends on the x2 Traefik story, maybe reverse proxy is not the right name +# TODO: Do we need to remove offers / outputs TF conditionally? +# TODO: We need to keep the COS API the same across products: feature in COS, COS Lite, and COS Dev -run "default_ingress_all_enabled" { - command = plan - - assert { - condition = length(juju_integration.ingress) == 4 - error_message = "Expected 4 ingress integrations (alertmanager, catalogue, loki, mimir), got ${length(juju_integration.ingress)}" - } - - # Grafana uses a separate count-based resource due to lifecycle replace_triggered_by - assert { - condition = length(juju_integration.grafana_ingress) == 1 - error_message = "Expected 1 grafana_ingress integration, got ${length(juju_integration.grafana_ingress)}" - } - - assert { - condition = length(juju_integration.traefik_route) == 2 - error_message = "Expected 2 traefik_route integrations (opentelemetry_collector, tempo), got ${length(juju_integration.traefik_route)}" - } -} - -# --- All ingress disabled --- +# --- reverse proxy: enabled - all ingress disabled --- -run "ingress_all_disabled" { +run "reverse_proxy_ingress_disabled" { command = plan variables { @@ -59,45 +41,14 @@ run "ingress_all_disabled" { } } -# --- Only grafana exposed --- +# --- mesh: enabled - all ingress disabled --- -run "ingress_only_grafana" { - command = plan - - variables { - ingress = { - alertmanager = false - catalogue = false - grafana = true - loki = false - mimir = false - opentelemetry_collector = false - tempo = false - } - } - - assert { - condition = length(juju_integration.ingress) == 0 - error_message = "Expected 0 ingress integrations, got ${length(juju_integration.ingress)}" - } - - assert { - condition = length(juju_integration.grafana_ingress) == 1 - error_message = "Expected 1 grafana_ingress integration, got ${length(juju_integration.grafana_ingress)}" - } - - assert { - condition = length(juju_integration.traefik_route) == 0 - error_message = "Expected 0 traefik_route integrations, got ${length(juju_integration.traefik_route)}" - } -} - -# --- Only tempo exposed (traefik_route) --- - -run "ingress_only_tempo" { +run "mesh_ingress_disabled" { command = plan variables { + mesh = { enabled = true } + reverse_proxy = { enabled = false } ingress = { alertmanager = false catalogue = false @@ -105,176 +56,22 @@ run "ingress_only_tempo" { loki = false mimir = false opentelemetry_collector = false - tempo = true - } - } - - assert { - condition = length(juju_integration.ingress) == 0 - error_message = "Expected 0 ingress integrations, got ${length(juju_integration.ingress)}" - } - - assert { - condition = length(juju_integration.traefik_route) == 1 - error_message = "Expected 1 traefik_route integration (tempo), got ${length(juju_integration.traefik_route)}" - } - - assert { - condition = contains(keys(juju_integration.traefik_route), "tempo") - error_message = "Expected traefik_route to contain 'tempo' key" - } -} - -# --- Partial override: disable alertmanager and tempo --- - -run "ingress_partial_override" { - command = plan - - variables { - ingress = { - alertmanager = false - tempo = false - } - } - - assert { - condition = length(juju_integration.ingress) == 3 - error_message = "Expected 3 ingress integrations (catalogue, loki, mimir), got ${length(juju_integration.ingress)}" - } - - assert { - condition = !contains(keys(juju_integration.ingress), "alertmanager") - error_message = "Expected ingress to NOT contain 'alertmanager' key" - } - - assert { - condition = length(juju_integration.grafana_ingress) == 1 - error_message = "Expected 1 grafana_ingress integration, got ${length(juju_integration.grafana_ingress)}" - } - - assert { - condition = length(juju_integration.traefik_route) == 1 - error_message = "Expected 1 traefik_route integration (opentelemetry_collector), got ${length(juju_integration.traefik_route)}" - } - - assert { - condition = contains(keys(juju_integration.traefik_route), "opentelemetry_collector") - error_message = "Expected traefik_route to contain 'opentelemetry_collector' key" - } -} - -# --- mesh_enabled: all ingress via istio, none via traefik --- - -run "mesh_ingress_all_enabled" { - command = plan - - variables { - mesh_enabled = true - internal_tls = false - } - - # Traefik ingress resources should be empty - assert { - condition = length(juju_integration.ingress) == 0 - error_message = "Expected 0 traefik ingress integrations when mesh is enabled, got ${length(juju_integration.ingress)}" - } - - assert { - condition = length(juju_integration.grafana_ingress) == 0 - error_message = "Expected 0 grafana_ingress integrations when mesh is enabled, got ${length(juju_integration.grafana_ingress)}" - } - - assert { - condition = length(juju_integration.traefik_route) == 0 - error_message = "Expected 0 traefik_route integrations when mesh is enabled, got ${length(juju_integration.traefik_route)}" - } - - # Istio ingress resources should be populated - assert { - condition = length(juju_integration.istio_ingress) == 4 - error_message = "Expected 4 istio_ingress integrations (alertmanager, catalogue, loki, mimir), got ${length(juju_integration.istio_ingress)}" - } - - assert { - condition = length(juju_integration.grafana_istio_ingress) == 1 - error_message = "Expected 1 grafana_istio_ingress integration, got ${length(juju_integration.grafana_istio_ingress)}" - } - - assert { - condition = length(juju_integration.istio_ingress_route) == 2 - error_message = "Expected 2 istio_ingress_route integrations (opentelemetry_collector, tempo), got ${length(juju_integration.istio_ingress_route)}" - } -} - -# --- mesh_enabled with partial ingress override --- - -run "mesh_ingress_partial" { - command = plan - - variables { - mesh_enabled = true - internal_tls = false - ingress = { - alertmanager = false - catalogue = true - grafana = false - loki = true - mimir = false - opentelemetry_collector = true tempo = false } } - # Traefik resources should all be empty - assert { - condition = length(juju_integration.ingress) == 0 - error_message = "Expected 0 traefik ingress integrations when mesh is enabled, got ${length(juju_integration.ingress)}" - } - - assert { - condition = length(juju_integration.grafana_ingress) == 0 - error_message = "Expected 0 grafana_ingress integrations when mesh is enabled" - } - - assert { - condition = length(juju_integration.traefik_route) == 0 - error_message = "Expected 0 traefik_route integrations when mesh is enabled" - } - - # Istio resources should respect the ingress toggles - assert { - condition = length(juju_integration.istio_ingress) == 2 - error_message = "Expected 2 istio_ingress integrations (catalogue, loki), got ${length(juju_integration.istio_ingress)}" - } - - assert { - condition = length(juju_integration.grafana_istio_ingress) == 0 - error_message = "Expected 0 grafana_istio_ingress integrations when grafana ingress is disabled" - } - - assert { - condition = length(juju_integration.istio_ingress_route) == 1 - error_message = "Expected 1 istio_ingress_route integration (opentelemetry_collector), got ${length(juju_integration.istio_ingress_route)}" - } -} - -# --- mesh disabled (default): no istio ingress resources --- - -run "no_mesh_no_istio_ingress" { - command = plan - assert { condition = length(juju_integration.istio_ingress) == 0 - error_message = "Expected 0 istio_ingress integrations when mesh is disabled, got ${length(juju_integration.istio_ingress)}" + error_message = "Expected 0 istio_ingress integrations, got ${length(juju_integration.istio_ingress)}" } assert { condition = length(juju_integration.grafana_istio_ingress) == 0 - error_message = "Expected 0 grafana_istio_ingress integrations when mesh is disabled" + error_message = "Expected 0 grafana_istio_ingress integrations, got ${length(juju_integration.grafana_istio_ingress)}" } assert { condition = length(juju_integration.istio_ingress_route) == 0 - error_message = "Expected 0 istio_ingress_route integrations when mesh is disabled" + error_message = "Expected 0 istio_ingress_route integrations, got ${length(juju_integration.istio_ingress_route)}" } } diff --git a/terraform/cos-dev/tests/mesh.tftest.hcl b/terraform/cos-dev/tests/mesh.tftest.hcl new file mode 100644 index 00000000..c3ff57eb --- /dev/null +++ b/terraform/cos-dev/tests/mesh.tftest.hcl @@ -0,0 +1,97 @@ +mock_provider "juju" {} + +variables { + model_uuid = "00000000-0000-0000-0000-000000000000" +} + +# --- mesh and reverse_proxy enabled: validation error --- + +run "mesh_and_reverse_proxy_enabled_fails" { + command = plan + + variables { + mesh = { enabled = false } + reverse_proxy = { enabled = true } + } + + expect_failures = [var.mesh.enabled] +} + +# --- mesh and reverse_proxy disabled: no validation error --- + +run "mesh_and_reverse_proxy_disabled" { + command = plan + + variables { + mesh = { enabled = false } + reverse_proxy = { enabled = false } + } +} + +# --- default: mesh disabled - no ingress via istio --- + +run "mesh_disabled" { + command = plan + + assert { + condition = length(module.istio-beacon) == 0 + error_message = "Expected no istio-beacon module when the mesh is disabled" + } + + assert { + condition = length(module.istio-ingress) == 0 + error_message = "Expected no istio-ingress module when the mesh is disabled" + } + + assert { + condition = length(juju_integration.istio_beacon) == 0 + error_message = "Expected no istio_beacon integrations when the mesh is disabled" + } + + assert { + condition = length(juju_integration.grafana_istio_ingress) == 0 + error_message = "Expected no grafana_istio_beacon integrations when the mesh is disabled" + } + + assert { + condition = length(juju_integration.istio_ingress_route) == 0 + error_message = "Expected no istio_ingress_route integrations when the mesh is disabled" + } +} + +# --- mesh enabled: ingress via istio --- + +run "mesh_enabled" { + command = plan + + variables { + mesh = { enabled = true } + reverse_proxy = { enabled = false } + } + + assert { + condition = length(module.istio-beacon) == 1 + error_message = "Expected istio-beacon module when the mesh is enabled" + } + + assert { + condition = length(module.istio-ingress) == 1 + error_message = "Expected istio-ingress module when the mesh is enabled" + } + + assert { + condition = length(juju_integration.istio_ingress) == 4 + error_message = "Expected 4 istio_ingress integrations (alertmanager, catalogue, loki, mimir), got ${length(juju_integration.istio_ingress)}" + } + + # Grafana uses a separate count-based resource due to lifecycle replace_triggered_by + assert { + condition = length(juju_integration.grafana_istio_ingress) == 1 + error_message = "Expected 1 grafana_istio_ingress integration, got ${length(juju_integration.grafana_istio_ingress)}" + } + + assert { + condition = length(juju_integration.istio_ingress_route) == 2 + error_message = "Expected 2 istio_ingress_route integrations (opentelemetry_collector, tempo), got ${length(juju_integration.istio_ingress_route)}" + } +} diff --git a/terraform/cos-dev/tests/mesh_enabled.tftest.hcl b/terraform/cos-dev/tests/mesh_enabled.tftest.hcl deleted file mode 100644 index 54f7448b..00000000 --- a/terraform/cos-dev/tests/mesh_enabled.tftest.hcl +++ /dev/null @@ -1,65 +0,0 @@ -mock_provider "juju" {} - -variables { - model_uuid = "00000000-0000-0000-0000-000000000000" -} - -# --- mesh_enabled=false (default): no istio modules or integrations deployed --- - -run "mesh_disabled_by_default" { - command = plan - - assert { - condition = length(module.istio-beacon) == 0 - error_message = "Expected no istio-beacon module when mesh is disabled" - } - - assert { - condition = length(module.istio-ingress) == 0 - error_message = "Expected no istio-ingress module when mesh is disabled" - } - - assert { - condition = length(juju_integration.istio_beacon) == 0 - error_message = "Expected no istio_beacon integrations when mesh is disabled" - } -} - -# --- mesh_enabled=true with internal_tls=false: istio modules and integrations deployed --- - -run "mesh_enabled_without_tls" { - command = plan - - variables { - mesh_enabled = true - internal_tls = false - } - - assert { - condition = length(module.istio-beacon) == 1 - error_message = "Expected istio-beacon module to be deployed when mesh is enabled" - } - - assert { - condition = length(module.istio-ingress) == 1 - error_message = "Expected istio-ingress module to be deployed when mesh is enabled" - } - - assert { - condition = length(juju_integration.istio_beacon) == 7 - error_message = "Expected 7 istio_beacon integrations (one per component) when mesh is enabled" - } -} - -# --- mesh_enabled=true with internal_tls=true (default): validation error --- - -run "mesh_enabled_with_internal_tls_fails" { - command = plan - - variables { - mesh_enabled = true - internal_tls = true - } - - expect_failures = [var.mesh_enabled] -} diff --git a/terraform/cos-dev/tests/reverse_proxy.tftest.hcl b/terraform/cos-dev/tests/reverse_proxy.tftest.hcl new file mode 100644 index 00000000..8e105460 --- /dev/null +++ b/terraform/cos-dev/tests/reverse_proxy.tftest.hcl @@ -0,0 +1,63 @@ +mock_provider "juju" {} + +variables { + model_uuid = "00000000-0000-0000-0000-000000000000" +} + +# --- default: reverse proxy enabled - ingress via traefik --- + +run "reverse_proxy_enabled" { + command = plan + + assert { + condition = length(module.traefik) == 0 + error_message = "Expected traefik module when the reverse proxy is enabled" + } + + assert { + condition = length(juju_integration.ingress) == 4 + error_message = "Expected 4 ingress integrations (alertmanager, catalogue, loki, mimir), got ${length(juju_integration.ingress)}" + } + + # Grafana uses a separate count-based resource due to lifecycle replace_triggered_by + assert { + condition = length(juju_integration.grafana_ingress) == 1 + error_message = "Expected 1 grafana_ingress integration, got ${length(juju_integration.grafana_ingress)}" + } + + assert { + condition = length(juju_integration.traefik_route) == 2 + error_message = "Expected 2 traefik_route integrations (opentelemetry_collector, tempo), got ${length(juju_integration.traefik_route)}" + } +} + +# --- reverse proxy disabled: no ingress via traefik --- + +run "reverse_proxy_disabled" { + command = plan + + variables { + mesh = { enabled = false } + reverse_proxy = { enabled = false } + } + + assert { + condition = length(module.traefik) == 0 + error_message = "Expected no traefik module when the reverse proxy is disabled" + } + + assert { + condition = length(juju_integration.istio_ingress) == 0 + error_message = "Expected 0 istio_ingress integrations when the reverse proxy is disabled, got ${length(juju_integration.istio_ingress)}" + } + + assert { + condition = length(juju_integration.grafana_istio_ingress) == 0 + error_message = "Expected 0 grafana_istio_ingress integrations when the reverse proxy is disabled" + } + + assert { + condition = length(juju_integration.istio_ingress_route) == 0 + error_message = "Expected 0 istio_ingress_route integrations when the reverse proxy is disabled" + } +} diff --git a/terraform/cos-dev/tests/topology_and_storage.tftest.hcl b/terraform/cos-dev/tests/topology_and_storage.tftest.hcl index ffb3b5c0..e29c3cdf 100644 --- a/terraform/cos-dev/tests/topology_and_storage.tftest.hcl +++ b/terraform/cos-dev/tests/topology_and_storage.tftest.hcl @@ -214,9 +214,11 @@ run "monolithic_s3" { s3_endpoint = "https://s3.example.com" s3_access_key = "access-key" s3_secret_key = "secret-key" - loki_bucket = "loki" - mimir_bucket = "mimir" - tempo_bucket = "tempo" + s3_buckets = { + loki = "loki" + mimir = "mimir" + tempo = "tempo" + } } assert { @@ -290,9 +292,11 @@ run "distributed_s3" { s3_endpoint = "https://s3.example.com" s3_access_key = "access-key" s3_secret_key = "secret-key" - loki_bucket = "loki" - mimir_bucket = "mimir" - tempo_bucket = "tempo" + s3_buckets = { + loki = "loki" + mimir = "mimir" + tempo = "tempo" + } } assert { diff --git a/terraform/cos-dev/variables.tf b/terraform/cos-dev/variables.tf index 9c5c6de2..1ee5b919 100644 --- a/terraform/cos-dev/variables.tf +++ b/terraform/cos-dev/variables.tf @@ -46,46 +46,50 @@ variable "storage_backend" { } } -# -------------- # Mesh configurations -------------- -variable "mesh_enabled" { - description = "Specify whether to enable the service mesh or not." - type = bool - default = false +# -------------- # Network configurations -------------- + +variable "mesh" { + description = "Configure the service mesh." + type = object({ + enabled = optional(bool, false) + cmr_urls = optional(object, { + # TODO: Consider making these null? + alermanager = "" + catalogue = "" + grafana = "" + loki = "" + mimir = "" + tempo = "" + }) + }) + default = {} validation { - condition = !(var.mesh_enabled && var.internal_tls) + condition = !(var.mesh.enabled && var.reverse_proxy.enabled) error_message = "mesh_enabled and internal_tls cannot both be enabled at the same time." } } -# -------------- # TLS configurations -------------- - -variable "internal_tls" { - description = "Specify whether to use TLS or not for internal COS communication. By default, TLS is enabled using self-signed-certificates" - type = bool - default = true -} - -variable "external_certificates_offer_url" { - description = "A Juju offer URL of a CA providing the 'tls_certificates' integration for Traefik to supply it with server certificates" - type = string - default = null +variable "reverse_proxy" { + description = "Configure the reverse proxy." + type = object({ + enabled = optional(bool, true) + cmr_urls = optional(object, { + certificates = "" + receive_ca_cert = "" + }) + }) + default = {} validation { condition = ( - (var.external_certificates_offer_url == null && var.external_ca_cert_offer_url == null) || - (var.external_certificates_offer_url != null && var.external_ca_cert_offer_url != null) + (var.reverse_proxy.cmr_urls.certificates == null && var.reverse_proxy.cmr_urls.receive_ca_cert == null) || + (var.reverse_proxy.cmr_urls.certificates != null && var.reverse_proxy.cmr_urls.receive_ca_cert != null) ) - error_message = "external_certificates_offer_url and external_ca_cert_offer_url must be supplied together (either both set or both null)." + error_message = "CMRs for certificates and receive_ca_cert must be supplied together (either both set or both null)." } } -variable "external_ca_cert_offer_url" { - description = "A Juju offer URL (e.g. admin/external-ca.send-ca-cert) of a CA providing the 'certificate_transfer' integration for applications to trust ingress via Traefik." - type = string - default = null -} - # -------------- # Ingress configurations -------------- variable "ingress" { @@ -124,34 +128,18 @@ variable "s3_secret_key" { default = null } -variable "loki_bucket" { - description = "Loki S3 bucket name" - type = string - default = "loki" -} - -variable "mimir_bucket" { - description = "Mimir S3 bucket name" - type = string - default = "mimir" -} - -variable "tempo_bucket" { - description = "Tempo S3 bucket name" - type = string - default = "tempo" -} - -variable "s3_integrator" { +# TODO: Move the refactor efforts into a separate PR that the mesh team can branch from to get mesh merged: +# 1. buckets, reverse_proxy +# 2. I couldn't group s3_ because this has "sensitive" vars. Check if this is 100% not possible. +# What if we made the whole s3 var sensitive? +variable "s3_buckets" { + description = "S3 bucket names for components" type = object({ - config = optional(map(string), {}) - constraints = optional(string, "arch=amd64") - revision = optional(number, null) - storage_directives = optional(map(string), {}) - units = optional(number, 1) + loki = optional(string, "loki") + mimir = optional(string, "mimir") + tempo = optional(string, "tempo") }) - default = {} - description = "Application configuration shared by all S3-integrators (one deployed per coordinated worker). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application" + default = {} } # -------------- # Application configurations -------------- @@ -334,6 +322,18 @@ variable "ssc" { description = "Application configuration for Self-signed-certificates. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application" } +variable "s3_integrator" { + type = object({ + config = optional(map(string), {}) + constraints = optional(string, "arch=amd64") + revision = optional(number, null) + storage_directives = optional(map(string), {}) + units = optional(number, 1) + }) + default = {} + description = "Application configuration shared by all S3-integrators (one deployed per coordinated worker). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application" +} + variable "tempo_coordinator" { type = object({ app_name = optional(string, "tempo") From 061d98dacfbedb0e36310b47b276c450768d8b4d Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Sun, 17 May 2026 16:34:01 +0200 Subject: [PATCH 03/21] chore --- .../tests/conditional_ingress.tftest.hcl | 1 + terraform/cos-dev/variables.tf | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl index 519bc9f0..faef5fbd 100644 --- a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl +++ b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl @@ -7,6 +7,7 @@ variables { # TODO: This feature also depends on the x2 Traefik story, maybe reverse proxy is not the right name # TODO: Do we need to remove offers / outputs TF conditionally? # TODO: We need to keep the COS API the same across products: feature in COS, COS Lite, and COS Dev +# TODO: For the conditional ingress story, do we support the same components between mesh and reverse proxy? If yes, then they can share the ingress var. Otherwise not, e.g. COS Lite # --- reverse proxy: enabled - all ingress disabled --- diff --git a/terraform/cos-dev/variables.tf b/terraform/cos-dev/variables.tf index 1ee5b919..7e466ff9 100644 --- a/terraform/cos-dev/variables.tf +++ b/terraform/cos-dev/variables.tf @@ -52,15 +52,15 @@ variable "mesh" { description = "Configure the service mesh." type = object({ enabled = optional(bool, false) - cmr_urls = optional(object, { - # TODO: Consider making these null? - alermanager = "" - catalogue = "" - grafana = "" - loki = "" - mimir = "" - tempo = "" - }) + cmr_urls = optional(object({ + alermanager = optional(string, null) + catalogue = optional(string, null) + grafana = optional(string, null) + loki = optional(string, null) + mimir = optional(string, null) + tempo = optional(string, null) + }), {} + ) }) default = {} @@ -74,10 +74,11 @@ variable "reverse_proxy" { description = "Configure the reverse proxy." type = object({ enabled = optional(bool, true) - cmr_urls = optional(object, { - certificates = "" - receive_ca_cert = "" - }) + cmr_urls = optional(object({ + certificates = optional(string, null) + receive_ca_cert = optional(string, null) + }), {} + ) }) default = {} From 7bada97b05d871b6d0d7ffc04c061c820eeacda8 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Mon, 18 May 2026 04:44:33 +0200 Subject: [PATCH 04/21] chore --- terraform/cos-dev/applications.tf | 32 +++++++++---------- terraform/cos-dev/integrations.tf | 26 +++++++-------- terraform/cos-dev/locals.tf | 20 ++++++++---- terraform/cos-dev/outputs.tf | 4 +-- terraform/cos-dev/tests/mesh.tftest.hcl | 18 +++++------ .../cos-dev/tests/reverse_proxy.tftest.hcl | 2 +- terraform/cos-dev/upgrades.tf | 12 +++++++ 7 files changed, 66 insertions(+), 48 deletions(-) diff --git a/terraform/cos-dev/applications.tf b/terraform/cos-dev/applications.tf index c2e8ca59..304a3b8e 100644 --- a/terraform/cos-dev/applications.tf +++ b/terraform/cos-dev/applications.tf @@ -35,30 +35,30 @@ module "grafana" { replace_triggers = [terraform_data.grafana_litestream_resource.id] } -module "istio-ingress" { +module "istio_ingress" { count = var.mesh.enabled ? 1 : 0 source = "git::https://github.com/canonical/istio-ingress-k8s-operator//terraform" - app_name = var.catalogue.app_name - channel = local.channels.catalogue - config = var.catalogue.config - constraints = var.catalogue.constraints + app_name = var.istio_ingress.app_name + channel = local.channels.istio_ingress + config = var.istio_ingress.config + constraints = var.istio_ingress.constraints model_uuid = var.model_uuid - revision = local.revisions.catalogue - storage_directives = var.catalogue.storage_directives - units = var.catalogue.units + revision = local.revisions.istio_ingress + storage_directives = var.istio_ingress.storage_directives + units = var.istio_ingress.units } -module "istio-beacon" { +module "istio_beacon" { count = var.mesh.enabled ? 1 : 0 source = "git::https://github.com/canonical/istio-beacon-k8s-operator//terraform" - app_name = var.catalogue.app_name - channel = local.channels.catalogue - config = var.catalogue.config - constraints = var.catalogue.constraints + app_name = var.istio_beacon.app_name + channel = local.channels.istio_beacon + config = var.istio_beacon.config + constraints = var.istio_beacon.constraints model_uuid = var.model_uuid - revision = local.revisions.catalogue - storage_directives = var.catalogue.storage_directives - units = var.catalogue.units + revision = local.revisions.istio_beacon + storage_directives = var.istio_beacon.storage_directives + units = var.istio_beacon.units } module "loki_coordinator" { diff --git a/terraform/cos-dev/integrations.tf b/terraform/cos-dev/integrations.tf index 82f9b721..5aa32815 100644 --- a/terraform/cos-dev/integrations.tf +++ b/terraform/cos-dev/integrations.tf @@ -655,8 +655,8 @@ resource "juju_integration" "ingress" { model_uuid = var.model_uuid application { - name = module.traefik.app_name - endpoint = module.traefik.endpoints.ingress + name = module.traefik[0].app_name + endpoint = module.traefik[0].endpoints.ingress } application { @@ -676,8 +676,8 @@ resource "juju_integration" "grafana_ingress" { } application { - name = module.traefik.app_name - endpoint = module.traefik.endpoints.ingress + name = module.traefik[0].app_name + endpoint = module.traefik[0].endpoints.ingress } lifecycle { replace_triggered_by = [terraform_data.grafana_ingress_interface, terraform_data.grafana_litestream_resource] } @@ -700,8 +700,8 @@ resource "juju_integration" "traefik_route" { model_uuid = var.model_uuid application { - name = module.traefik.app_name - endpoint = module.traefik.endpoints.traefik_route + name = module.traefik[0].app_name + endpoint = module.traefik[0].endpoints.traefik_route } application { @@ -863,8 +863,8 @@ resource "juju_integration" "traefik_receive_ca_certificate" { } application { - name = module.traefik.app_name - endpoint = module.traefik.endpoints.receive_ca_cert + name = module.traefik[0].app_name + endpoint = module.traefik[0].endpoints.receive_ca_cert } } @@ -875,12 +875,12 @@ resource "juju_integration" "external_traefik_certificates" { model_uuid = var.model_uuid application { - offer_url = var.external_certificates_offer_url + offer_url = var.reverse_proxy.cmr_urls.certificates } application { - name = module.traefik.app_name - endpoint = module.traefik.endpoints.certificates + name = module.traefik[0].app_name + endpoint = module.traefik[0].endpoints.certificates } } @@ -889,7 +889,7 @@ resource "juju_integration" "external_grafana_ca_cert" { model_uuid = var.model_uuid application { - offer_url = var.external_ca_cert_offer_url + offer_url = var.reverse_proxy.cmr_urls.receive_ca_cert } application { @@ -903,7 +903,7 @@ resource "juju_integration" "external_otelcol_ca_cert" { model_uuid = var.model_uuid application { - offer_url = var.external_ca_cert_offer_url + offer_url = var.reverse_proxy.cmr_urls.receive_ca_cert } application { diff --git a/terraform/cos-dev/locals.tf b/terraform/cos-dev/locals.tf index b821a6a0..14f75b58 100644 --- a/terraform/cos-dev/locals.tf +++ b/terraform/cos-dev/locals.tf @@ -2,13 +2,15 @@ locals { reverse_proxy_termination = var.reverse_proxy.enabled && var.reverse_proxy.cmr_urls.certificates != null ? true : false traefik_base = "ubuntu@20.04" tracks = { - alertmanager = "dev" - catalogue = "dev" - grafana = "dev" - loki = "dev" - mimir = "dev" - otelcol = "dev" - tempo = "dev" + alertmanager = "dev" + catalogue = "dev" + grafana = "dev" + istio_beacon = "dev" + istio_ingress = "dev" + loki = "dev" + mimir = "dev" + otelcol = "dev" + tempo = "dev" s3_integrator = "2" seaweedfs = "latest" @@ -19,6 +21,8 @@ locals { alertmanager = "${local.tracks.alertmanager}/${var.risk}" catalogue = "${local.tracks.catalogue}/${var.risk}" grafana = "${local.tracks.grafana}/${var.risk}" + istio_beacon = "${local.tracks.istio_beacon}/${var.risk}" + istio_ingress = "${local.tracks.istio_ingress}/${var.risk}" loki = "${local.tracks.loki}/${var.risk}" mimir = "${local.tracks.mimir}/${var.risk}" otelcol = "${local.tracks.otelcol}/${var.risk}" @@ -32,6 +36,8 @@ locals { alertmanager = var.alertmanager.revision != null ? var.alertmanager.revision : data.juju_charm.alertmanager_info.revision catalogue = var.catalogue.revision != null ? var.catalogue.revision : data.juju_charm.catalogue_info.revision grafana = var.grafana.revision != null ? var.grafana.revision : data.juju_charm.grafana_info.revision + istio_beacon = var.istio_beacon.revision != null ? var.istio_beacon.revision : data.juju_charm.istio_beacon_info.revision + istio_ingress = var.istio_ingress.revision != null ? var.istio_ingress.revision : data.juju_charm.istio_ingress_info.revision loki_coordinator = var.loki_coordinator.revision != null ? var.loki_coordinator.revision : data.juju_charm.loki_coordinator_info.revision loki_worker = var.loki_worker.revision != null ? var.loki_worker.revision : data.juju_charm.loki_worker_info.revision mimir_coordinator = var.mimir_coordinator.revision != null ? var.mimir_coordinator.revision : data.juju_charm.mimir_coordinator_info.revision diff --git a/terraform/cos-dev/outputs.tf b/terraform/cos-dev/outputs.tf index 733e3bdc..6de7ddc5 100644 --- a/terraform/cos-dev/outputs.tf +++ b/terraform/cos-dev/outputs.tf @@ -34,8 +34,8 @@ output "components" { alertmanager = module.alertmanager catalogue = module.catalogue grafana = module.grafana - grafana = try(module.istio_beacon[0], null) - grafana = try(module.istio_ingress[0], null) + istio_beacon = try(module.istio_beacon[0], null) + istio_ingress = try(module.istio_ingress[0], null) loki_coordinator = module.loki_coordinator loki_worker = try(module.loki_worker[0], null) loki_worker_backend = try(module.loki_worker_backend[0], null) diff --git a/terraform/cos-dev/tests/mesh.tftest.hcl b/terraform/cos-dev/tests/mesh.tftest.hcl index c3ff57eb..2ceba568 100644 --- a/terraform/cos-dev/tests/mesh.tftest.hcl +++ b/terraform/cos-dev/tests/mesh.tftest.hcl @@ -10,7 +10,7 @@ run "mesh_and_reverse_proxy_enabled_fails" { command = plan variables { - mesh = { enabled = false } + mesh = { enabled = true } reverse_proxy = { enabled = true } } @@ -34,13 +34,13 @@ run "mesh_disabled" { command = plan assert { - condition = length(module.istio-beacon) == 0 - error_message = "Expected no istio-beacon module when the mesh is disabled" + condition = length(module.istio_beacon) == 0 + error_message = "Expected no istio_beacon module when the mesh is disabled" } assert { - condition = length(module.istio-ingress) == 0 - error_message = "Expected no istio-ingress module when the mesh is disabled" + condition = length(module.istio_ingress) == 0 + error_message = "Expected no istio_ingress module when the mesh is disabled" } assert { @@ -70,13 +70,13 @@ run "mesh_enabled" { } assert { - condition = length(module.istio-beacon) == 1 - error_message = "Expected istio-beacon module when the mesh is enabled" + condition = length(module.istio_beacon) == 1 + error_message = "Expected istio_beacon module when the mesh is enabled" } assert { - condition = length(module.istio-ingress) == 1 - error_message = "Expected istio-ingress module when the mesh is enabled" + condition = length(module.istio_ingress) == 1 + error_message = "Expected istio_ingress module when the mesh is enabled" } assert { diff --git a/terraform/cos-dev/tests/reverse_proxy.tftest.hcl b/terraform/cos-dev/tests/reverse_proxy.tftest.hcl index 8e105460..53058b8c 100644 --- a/terraform/cos-dev/tests/reverse_proxy.tftest.hcl +++ b/terraform/cos-dev/tests/reverse_proxy.tftest.hcl @@ -10,7 +10,7 @@ run "reverse_proxy_enabled" { command = plan assert { - condition = length(module.traefik) == 0 + condition = length(module.traefik) == 1 error_message = "Expected traefik module when the reverse proxy is enabled" } diff --git a/terraform/cos-dev/upgrades.tf b/terraform/cos-dev/upgrades.tf index 270c68f8..90c733ae 100644 --- a/terraform/cos-dev/upgrades.tf +++ b/terraform/cos-dev/upgrades.tf @@ -38,6 +38,18 @@ data "juju_charm" "grafana_info" { base = var.base } +data "juju_charm" "istio_beacon_info" { + charm = "istio-beacon-k8s" + channel = local.channels.istio_beacon + base = var.base +} + +data "juju_charm" "istio_ingress_info" { + charm = "istio-ingress-k8s" + channel = local.channels.istio_ingress + base = var.base +} + data "juju_charm" "loki_coordinator_info" { charm = "loki-coordinator-k8s" channel = local.channels.loki From 18b264150fb84280f5d693acfb122db8edda8a87 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Tue, 19 May 2026 12:09:04 -0400 Subject: [PATCH 05/21] chore --- terraform/cos-dev/README.md | 13 +++++++------ terraform/cos-dev/tests/reverse_proxy.tftest.hcl | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/terraform/cos-dev/README.md b/terraform/cos-dev/README.md index fcb03a75..9ff8b7be 100644 --- a/terraform/cos-dev/README.md +++ b/terraform/cos-dev/README.md @@ -19,6 +19,8 @@ This module is intended for development and testing environments where full HA i | [alertmanager](#module\_alertmanager) | git::https://github.com/canonical/alertmanager-k8s-operator//terraform | n/a | | [catalogue](#module\_catalogue) | git::https://github.com/canonical/catalogue-k8s-operator//terraform | n/a | | [grafana](#module\_grafana) | git::https://github.com/canonical/grafana-k8s-operator//terraform | n/a | +| [istio\_beacon](#module\_istio\_beacon) | git::https://github.com/canonical/istio-beacon-k8s-operator//terraform | n/a | +| [istio\_ingress](#module\_istio\_ingress) | git::https://github.com/canonical/istio-ingress-k8s-operator//terraform | n/a | | [loki\_coordinator](#module\_loki\_coordinator) | git::https://github.com/canonical/loki-operators//coordinator/terraform | n/a | | [loki\_worker](#module\_loki\_worker) | git::https://github.com/canonical/loki-operators//worker/terraform | n/a | | [loki\_worker\_backend](#module\_loki\_worker\_backend) | git::https://github.com/canonical/loki-operators//worker/terraform | n/a | @@ -49,28 +51,27 @@ This module is intended for development and testing environments where full HA i | [alertmanager](#input\_alertmanager) | Application configuration for Alertmanager. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "alertmanager")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [base](#input\_base) | The operating system on which to deploy. E.g. ubuntu@24.04. Check Charmhub for per-charm base support. | `string` | `"ubuntu@24.04"` | no | | [catalogue](#input\_catalogue) | Application configuration for Catalogue. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "catalogue")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | -| [external\_ca\_cert\_offer\_url](#input\_external\_ca\_cert\_offer\_url) | A Juju offer URL (e.g. admin/external-ca.send-ca-cert) of a CA providing the 'certificate\_transfer' integration for applications to trust ingress via Traefik. | `string` | `null` | no | -| [external\_certificates\_offer\_url](#input\_external\_certificates\_offer\_url) | A Juju offer URL of a CA providing the 'tls\_certificates' integration for Traefik to supply it with server certificates | `string` | `null` | no | | [grafana](#input\_grafana) | Application configuration for Grafana. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "grafana")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [ingress](#input\_ingress) | Per-component toggle for ingress integrations |
object({
alertmanager = optional(bool, true)
catalogue = optional(bool, true)
grafana = optional(bool, true)
loki = optional(bool, true)
mimir = optional(bool, true)
opentelemetry_collector = optional(bool, true)
tempo = optional(bool, true)
})
| `{}` | no | -| [internal\_tls](#input\_internal\_tls) | Specify whether to use TLS or not for internal COS communication. By default, TLS is enabled using self-signed-certificates | `bool` | `true` | no | -| [loki\_bucket](#input\_loki\_bucket) | Loki S3 bucket name | `string` | `"loki"` | no | +| [istio\_beacon](#input\_istio\_beacon) | Application configuration for istio-beacon. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "istio-beacon")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | +| [istio\_ingress](#input\_istio\_ingress) | Application configuration for istio-ingress. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "istio-ingress")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [loki\_coordinator](#input\_loki\_coordinator) | Application configuration for the Loki coordinator. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "loki")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [loki\_worker](#input\_loki\_worker) | Application configuration for the Loki worker(s). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "loki-worker")
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
# Monolithic mode (role-all)
config = optional(map(string), {})
storage_directives = optional(map(string), {})
units = optional(number, 1)
# Distributed mode
backend_config = optional(map(string), {})
read_config = optional(map(string), {})
write_config = optional(map(string), {})
backend_storage_directives = optional(map(string), {})
read_storage_directives = optional(map(string), {})
write_storage_directives = optional(map(string), {})
backend_units = optional(number, 1)
read_units = optional(number, 1)
write_units = optional(number, 1)
})
| `{}` | no | -| [mimir\_bucket](#input\_mimir\_bucket) | Mimir S3 bucket name | `string` | `"mimir"` | no | +| [mesh](#input\_mesh) | Configure the service mesh. |
object({
enabled = optional(bool, false)
cmr_urls = optional(object({
alermanager = optional(string, null)
catalogue = optional(string, null)
grafana = optional(string, null)
loki = optional(string, null)
mimir = optional(string, null)
tempo = optional(string, null)
}), {}
)
})
| `{}` | no | | [mimir\_coordinator](#input\_mimir\_coordinator) | Application configuration for the Mimir coordinator. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "mimir")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [mimir\_worker](#input\_mimir\_worker) | Application configuration for the Mimir worker(s). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "mimir-worker")
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
# Monolithic mode (role-all)
config = optional(map(string), {})
storage_directives = optional(map(string), {})
units = optional(number, 1)
# Distributed mode
backend_config = optional(map(string), {})
read_config = optional(map(string), {})
write_config = optional(map(string), {})
backend_storage_directives = optional(map(string), {})
read_storage_directives = optional(map(string), {})
write_storage_directives = optional(map(string), {})
backend_units = optional(number, 1)
read_units = optional(number, 1)
write_units = optional(number, 1)
})
| `{}` | no | | [model\_uuid](#input\_model\_uuid) | Reference to an existing model resource or data source for the model to deploy to | `string` | n/a | yes | | [opentelemetry\_collector](#input\_opentelemetry\_collector) | Application configuration for OpenTelemetry Collector. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "otelcol")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | +| [reverse\_proxy](#input\_reverse\_proxy) | Configure the reverse proxy. |
object({
enabled = optional(bool, true)
cmr_urls = optional(object({
certificates = optional(string, null)
receive_ca_cert = optional(string, null)
}), {}
)
})
| `{}` | no | | [risk](#input\_risk) | Risk level that the applications are (unless overwritten by individual channels) deployed from | `string` | `"edge"` | no | | [s3\_access\_key](#input\_s3\_access\_key) | S3 access-key credential. Required when storage\_backend is 's3'. | `string` | `null` | no | +| [s3\_buckets](#input\_s3\_buckets) | S3 bucket names for components |
object({
loki = optional(string, "loki")
mimir = optional(string, "mimir")
tempo = optional(string, "tempo")
})
| `{}` | no | | [s3\_endpoint](#input\_s3\_endpoint) | S3 endpoint URL. Required when storage\_backend is 's3'. | `string` | `null` | no | | [s3\_integrator](#input\_s3\_integrator) | Application configuration shared by all S3-integrators (one deployed per coordinated worker). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [s3\_secret\_key](#input\_s3\_secret\_key) | S3 secret-key credential. Required when storage\_backend is 's3'. | `string` | `null` | no | | [seaweedfs](#input\_seaweedfs) | Application configuration for SeaweedFS. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "seaweedfs")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [ssc](#input\_ssc) | Application configuration for Self-signed-certificates. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "ca")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [storage\_backend](#input\_storage\_backend) | Storage backend: 'seaweedfs' (built-in S3-compatible storage) or 's3' (external S3/Ceph via s3-integrator). | `string` | `"seaweedfs"` | no | -| [tempo\_bucket](#input\_tempo\_bucket) | Tempo S3 bucket name | `string` | `"tempo"` | no | | [tempo\_coordinator](#input\_tempo\_coordinator) | Application configuration for the Tempo coordinator. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "tempo")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [tempo\_worker](#input\_tempo\_worker) | Application configuration for the Tempo worker(s). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "tempo-worker")
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
# Monolithic mode (role-all)
config = optional(map(string), {})
storage_directives = optional(map(string), {})
units = optional(number, 1)
# Distributed mode
querier_config = optional(map(string), {})
query_frontend_config = optional(map(string), {})
ingester_config = optional(map(string), {})
distributor_config = optional(map(string), {})
compactor_config = optional(map(string), {})
metrics_generator_config = optional(map(string), {})
querier_storage_directives = optional(map(string), {})
query_frontend_storage_directives = optional(map(string), {})
ingester_storage_directives = optional(map(string), {})
distributor_storage_directives = optional(map(string), {})
compactor_storage_directives = optional(map(string), {})
metrics_generator_storage_directives = optional(map(string), {})
querier_units = optional(number, 1)
query_frontend_units = optional(number, 1)
ingester_units = optional(number, 1)
distributor_units = optional(number, 1)
compactor_units = optional(number, 1)
metrics_generator_units = optional(number, 1)
})
| `{}` | no | | [topology](#input\_topology) | Deployment topology: 'monolithic' (single role-all worker per component) or 'distributed' (separate workers per role). | `string` | `"monolithic"` | no | diff --git a/terraform/cos-dev/tests/reverse_proxy.tftest.hcl b/terraform/cos-dev/tests/reverse_proxy.tftest.hcl index 53058b8c..357fd464 100644 --- a/terraform/cos-dev/tests/reverse_proxy.tftest.hcl +++ b/terraform/cos-dev/tests/reverse_proxy.tftest.hcl @@ -8,7 +8,7 @@ variables { run "reverse_proxy_enabled" { command = plan - + assert { condition = length(module.traefik) == 1 error_message = "Expected traefik module when the reverse proxy is enabled" @@ -40,7 +40,7 @@ run "reverse_proxy_disabled" { mesh = { enabled = false } reverse_proxy = { enabled = false } } - + assert { condition = length(module.traefik) == 0 error_message = "Expected no traefik module when the reverse proxy is disabled" From 02ac17aa4e61f0b4cd4eef8e06b672063f3cb568 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Wed, 20 May 2026 11:47:58 -0400 Subject: [PATCH 06/21] feat: Generic COS assertions function (#351) --- .../tls_external/test_upgrade_cos_tls_external.py | 15 +++------------ .../cos/tls_full/test_upgrade_cos_tls_full.py | 15 +++------------ .../tls_internal/test_upgrade_cos_tls_internal.py | 12 +++--------- .../cos/tls_none/test_upgrade_cos_tls_none.py | 12 +++--------- .../test_upgrade_cos_lite_tls_external.py | 14 +++----------- .../tls_full/test_upgrade_cos_lite_tls_full.py | 14 +++----------- .../test_upgrade_cos_lite_tls_internal.py | 8 +++----- .../tls_none/test_upgrade_cos_lite_tls_none.py | 8 +++----- tests/integration/helpers.py | 15 +++++++++++++++ 9 files changed, 39 insertions(+), 74 deletions(-) diff --git a/tests/integration/cos/tls_external/test_upgrade_cos_tls_external.py b/tests/integration/cos/tls_external/test_upgrade_cos_tls_external.py index c360df87..33b46e17 100644 --- a/tests/integration/cos/tls_external/test_upgrade_cos_tls_external.py +++ b/tests/integration/cos/tls_external/test_upgrade_cos_tls_external.py @@ -8,12 +8,7 @@ from pathlib import Path import jubilant -from helpers import ( - catalogue_apps_are_reachable, - get_tls_context, - no_errors_in_otelcol_logs, - wait_for_active_idle_without_error, -) +from helpers import generic_assertions, no_errors_in_otelcol_logs TRACK_2_TF_FILE = Path(__file__).parent.resolve() / "track-2.tf" TRACK_DEV_TF_FILE = Path(__file__).parent.resolve() / "track-dev.tf" @@ -36,9 +31,7 @@ def test_deploy_from_track_2( # GIVEN a module deployed from track 2 tf_manager.init(TRACK_2_TF_FILE) tf_manager.apply(ca_model=ca_model.model, cos_model=cos_model.model, **S3_ENDPOINT) - wait_for_active_idle_without_error([cos_model], timeout=5400) - tls_ctx = get_tls_context(tmp_path, ca_model, "self-signed-certificates") - catalogue_apps_are_reachable(cos_model, tls_ctx) + generic_assertions(cos_model, ca_model, tmp_path) no_errors_in_otelcol_logs(cos_model) @@ -50,7 +43,5 @@ def test_deploy_to_track_dev( tf_manager.apply(ca_model=ca_model.model, cos_model=cos_model.model, **S3_ENDPOINT) # THEN the model is upgraded and is healthy - wait_for_active_idle_without_error([ca_model, cos_model]) - tls_ctx = get_tls_context(tmp_path, ca_model, "self-signed-certificates") - catalogue_apps_are_reachable(cos_model, tls_ctx) + generic_assertions(cos_model, ca_model, tmp_path) no_errors_in_otelcol_logs(cos_model) diff --git a/tests/integration/cos/tls_full/test_upgrade_cos_tls_full.py b/tests/integration/cos/tls_full/test_upgrade_cos_tls_full.py index a817a76b..9a48df79 100644 --- a/tests/integration/cos/tls_full/test_upgrade_cos_tls_full.py +++ b/tests/integration/cos/tls_full/test_upgrade_cos_tls_full.py @@ -8,12 +8,7 @@ from pathlib import Path import jubilant -from helpers import ( - catalogue_apps_are_reachable, - get_tls_context, - no_errors_in_otelcol_logs, - wait_for_active_idle_without_error, -) +from helpers import generic_assertions, no_errors_in_otelcol_logs TRACK_2_TF_FILE = Path(__file__).parent.resolve() / "track-2.tf" TRACK_DEV_TF_FILE = Path(__file__).parent.resolve() / "track-dev.tf" @@ -36,9 +31,7 @@ def test_deploy_from_track_2( # GIVEN a module deployed from track 2 tf_manager.init(TRACK_2_TF_FILE) tf_manager.apply(ca_model=ca_model.model, cos_model=cos_model.model, **S3_ENDPOINT) - wait_for_active_idle_without_error([cos_model], timeout=5400) - tls_ctx = get_tls_context(tmp_path, ca_model, "self-signed-certificates") - catalogue_apps_are_reachable(cos_model, tls_ctx) + generic_assertions(cos_model, ca_model, tmp_path) no_errors_in_otelcol_logs(cos_model) @@ -50,7 +43,5 @@ def test_deploy_to_track_dev( tf_manager.apply(ca_model=ca_model.model, cos_model=cos_model.model, **S3_ENDPOINT) # THEN the model is upgraded and is healthy - wait_for_active_idle_without_error([ca_model, cos_model]) - tls_ctx = get_tls_context(tmp_path, ca_model, "self-signed-certificates") - catalogue_apps_are_reachable(cos_model, tls_ctx) + generic_assertions(cos_model, ca_model, tmp_path) no_errors_in_otelcol_logs(cos_model) diff --git a/tests/integration/cos/tls_internal/test_upgrade_cos_tls_internal.py b/tests/integration/cos/tls_internal/test_upgrade_cos_tls_internal.py index d288c0e2..c0425362 100644 --- a/tests/integration/cos/tls_internal/test_upgrade_cos_tls_internal.py +++ b/tests/integration/cos/tls_internal/test_upgrade_cos_tls_internal.py @@ -8,11 +8,7 @@ from pathlib import Path import jubilant -from helpers import ( - catalogue_apps_are_reachable, - no_errors_in_otelcol_logs, - wait_for_active_idle_without_error, -) +from helpers import generic_assertions, no_errors_in_otelcol_logs TRACK_2_TF_FILE = Path(__file__).parent.resolve() / "track-2.tf" TRACK_DEV_TF_FILE = Path(__file__).parent.resolve() / "track-dev.tf" @@ -33,8 +29,7 @@ def test_deploy_from_track_2(tf_manager, cos_model: jubilant.Juju): # GIVEN a module deployed from track 2 tf_manager.init(TRACK_2_TF_FILE) tf_manager.apply(model=cos_model.model, **S3_ENDPOINT) - wait_for_active_idle_without_error([cos_model], timeout=5400) - catalogue_apps_are_reachable(cos_model) + generic_assertions(cos_model) no_errors_in_otelcol_logs(cos_model) @@ -44,6 +39,5 @@ def test_deploy_to_track_dev(tf_manager, cos_model: jubilant.Juju): tf_manager.apply(model=cos_model.model, **S3_ENDPOINT) # THEN the model is upgraded and is healthy - wait_for_active_idle_without_error([cos_model]) - catalogue_apps_are_reachable(cos_model) + generic_assertions(cos_model) no_errors_in_otelcol_logs(cos_model) diff --git a/tests/integration/cos/tls_none/test_upgrade_cos_tls_none.py b/tests/integration/cos/tls_none/test_upgrade_cos_tls_none.py index eaf985dd..7f33b146 100644 --- a/tests/integration/cos/tls_none/test_upgrade_cos_tls_none.py +++ b/tests/integration/cos/tls_none/test_upgrade_cos_tls_none.py @@ -8,11 +8,7 @@ from pathlib import Path import jubilant -from helpers import ( - catalogue_apps_are_reachable, - no_errors_in_otelcol_logs, - wait_for_active_idle_without_error, -) +from helpers import generic_assertions, no_errors_in_otelcol_logs TRACK_2_TF_FILE = Path(__file__).parent.resolve() / "track-2.tf" TRACK_DEV_TF_FILE = Path(__file__).parent.resolve() / "track-dev.tf" @@ -33,8 +29,7 @@ def test_deploy_from_track_2(tf_manager, cos_model: jubilant.Juju): # GIVEN a module deployed from track 2 tf_manager.init(TRACK_2_TF_FILE) tf_manager.apply(model=cos_model.model, **S3_ENDPOINT) - wait_for_active_idle_without_error([cos_model], timeout=5400) - catalogue_apps_are_reachable(cos_model) + generic_assertions(cos_model) no_errors_in_otelcol_logs(cos_model) @@ -44,6 +39,5 @@ def test_deploy_to_track_dev(tf_manager, cos_model: jubilant.Juju): tf_manager.apply(model=cos_model.model, **S3_ENDPOINT) # THEN the model is upgraded and is healthy - wait_for_active_idle_without_error([cos_model]) - catalogue_apps_are_reachable(cos_model) + generic_assertions(cos_model) no_errors_in_otelcol_logs(cos_model) diff --git a/tests/integration/cos_lite/tls_external/test_upgrade_cos_lite_tls_external.py b/tests/integration/cos_lite/tls_external/test_upgrade_cos_lite_tls_external.py index bded1290..46e5285a 100644 --- a/tests/integration/cos_lite/tls_external/test_upgrade_cos_lite_tls_external.py +++ b/tests/integration/cos_lite/tls_external/test_upgrade_cos_lite_tls_external.py @@ -7,11 +7,7 @@ from pathlib import Path import jubilant -from helpers import ( - catalogue_apps_are_reachable, - get_tls_context, - wait_for_active_idle_without_error, -) +from helpers import generic_assertions TRACK_2_TF_FILE = Path(__file__).parent.resolve() / "track-2.tf" TRACK_DEV_TF_FILE = Path(__file__).parent.resolve() / "track-dev.tf" @@ -23,9 +19,7 @@ def test_deploy_from_track_2( # GIVEN a module deployed from track 2 tf_manager.init(TRACK_2_TF_FILE) tf_manager.apply(ca_model=ca_model.model, cos_model=cos_model.model) - wait_for_active_idle_without_error([ca_model, cos_model], timeout=60 * 60) - tls_ctx = get_tls_context(tmp_path, ca_model, "self-signed-certificates") - catalogue_apps_are_reachable(cos_model, tls_ctx) + generic_assertions(cos_model, ca_model, tmp_path) def test_deploy_to_track_dev( @@ -36,6 +30,4 @@ def test_deploy_to_track_dev( tf_manager.apply(ca_model=ca_model.model, cos_model=cos_model.model) # THEN the model is upgraded and is healthy - wait_for_active_idle_without_error([ca_model, cos_model]) - tls_ctx = get_tls_context(tmp_path, ca_model, "self-signed-certificates") - catalogue_apps_are_reachable(cos_model, tls_ctx) + generic_assertions(cos_model, ca_model, tmp_path) diff --git a/tests/integration/cos_lite/tls_full/test_upgrade_cos_lite_tls_full.py b/tests/integration/cos_lite/tls_full/test_upgrade_cos_lite_tls_full.py index 0d742340..e834b153 100644 --- a/tests/integration/cos_lite/tls_full/test_upgrade_cos_lite_tls_full.py +++ b/tests/integration/cos_lite/tls_full/test_upgrade_cos_lite_tls_full.py @@ -7,11 +7,7 @@ from pathlib import Path import jubilant -from helpers import ( - catalogue_apps_are_reachable, - get_tls_context, - wait_for_active_idle_without_error, -) +from helpers import generic_assertions TRACK_2_TF_FILE = Path(__file__).parent.resolve() / "track-2.tf" TRACK_DEV_TF_FILE = Path(__file__).parent.resolve() / "track-dev.tf" @@ -23,9 +19,7 @@ def test_deploy_from_track_2( # GIVEN a module deployed from track 2 tf_manager.init(TRACK_2_TF_FILE) tf_manager.apply(ca_model=ca_model.model, cos_model=cos_model.model) - wait_for_active_idle_without_error([ca_model, cos_model], timeout=60 * 60) - tls_ctx = get_tls_context(tmp_path, ca_model, "self-signed-certificates") - catalogue_apps_are_reachable(cos_model, tls_ctx) + generic_assertions(cos_model, ca_model, tmp_path) def test_deploy_to_track_dev( @@ -36,6 +30,4 @@ def test_deploy_to_track_dev( tf_manager.apply(ca_model=ca_model.model, cos_model=cos_model.model) # THEN the model is upgraded and is healthy - wait_for_active_idle_without_error([ca_model, cos_model]) - tls_ctx = get_tls_context(tmp_path, ca_model, "self-signed-certificates") - catalogue_apps_are_reachable(cos_model, tls_ctx) + generic_assertions(cos_model, ca_model, tmp_path) diff --git a/tests/integration/cos_lite/tls_internal/test_upgrade_cos_lite_tls_internal.py b/tests/integration/cos_lite/tls_internal/test_upgrade_cos_lite_tls_internal.py index 9e49fea8..b5d91db6 100644 --- a/tests/integration/cos_lite/tls_internal/test_upgrade_cos_lite_tls_internal.py +++ b/tests/integration/cos_lite/tls_internal/test_upgrade_cos_lite_tls_internal.py @@ -7,7 +7,7 @@ from pathlib import Path import jubilant -from helpers import catalogue_apps_are_reachable, wait_for_active_idle_without_error +from helpers import generic_assertions TRACK_2_TF_FILE = Path(__file__).parent.resolve() / "track-2.tf" TRACK_DEV_TF_FILE = Path(__file__).parent.resolve() / "track-dev.tf" @@ -17,8 +17,7 @@ def test_deploy_from_track_2(tf_manager, cos_model: jubilant.Juju): # GIVEN a module deployed from track 2 tf_manager.init(TRACK_2_TF_FILE) tf_manager.apply(model=cos_model.model) - wait_for_active_idle_without_error([cos_model], timeout=60 * 60) - catalogue_apps_are_reachable(cos_model) + generic_assertions(cos_model) def test_deploy_to_track_dev(tf_manager, cos_model: jubilant.Juju): @@ -27,5 +26,4 @@ def test_deploy_to_track_dev(tf_manager, cos_model: jubilant.Juju): tf_manager.apply(model=cos_model.model) # THEN the model is upgraded and is healthy - wait_for_active_idle_without_error([cos_model]) - catalogue_apps_are_reachable(cos_model) + generic_assertions(cos_model) diff --git a/tests/integration/cos_lite/tls_none/test_upgrade_cos_lite_tls_none.py b/tests/integration/cos_lite/tls_none/test_upgrade_cos_lite_tls_none.py index f253fa9b..0d9eae1c 100644 --- a/tests/integration/cos_lite/tls_none/test_upgrade_cos_lite_tls_none.py +++ b/tests/integration/cos_lite/tls_none/test_upgrade_cos_lite_tls_none.py @@ -7,7 +7,7 @@ from pathlib import Path import jubilant -from helpers import catalogue_apps_are_reachable, wait_for_active_idle_without_error +from helpers import generic_assertions TRACK_2_TF_FILE = Path(__file__).parent.resolve() / "track-2.tf" TRACK_DEV_TF_FILE = Path(__file__).parent.resolve() / "track-dev.tf" @@ -17,8 +17,7 @@ def test_deploy_from_track_2(tf_manager, cos_model: jubilant.Juju): # GIVEN a module deployed from track 2 tf_manager.init(TRACK_2_TF_FILE) tf_manager.apply(model=cos_model.model) - wait_for_active_idle_without_error([cos_model], timeout=60 * 60) - catalogue_apps_are_reachable(cos_model) + generic_assertions(cos_model) def test_deploy_to_track_dev(tf_manager, cos_model: jubilant.Juju): @@ -27,5 +26,4 @@ def test_deploy_to_track_dev(tf_manager, cos_model: jubilant.Juju): tf_manager.apply(model=cos_model.model) # THEN the model is upgraded and is healthy - wait_for_active_idle_without_error([cos_model]) - catalogue_apps_are_reachable(cos_model) + generic_assertions(cos_model) diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 4be9a10b..6c0f8ac9 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -43,6 +43,21 @@ def destroy(self, **kwargs): subprocess.run(shlex.split(cmd_str), check=True) +def generic_assertions( + cos_model: jubilant.Juju, + ca_model: jubilant.Juju | None = None, + temp_path: Path | None = None, +): + # generic assertions that are shared between products: cos, cos-lite + wait_for_active_idle_without_error([ca_model, cos_model], timeout=60 * 60) + if ca_model: + assert temp_path is not None, "temp_path is required when ca_model is provided" + tls_ctx = get_tls_context(temp_path, ca_model, "self-signed-certificates") + else: + tls_ctx = None + catalogue_apps_are_reachable(cos_model, tls_ctx) + + def wait_for_active_idle_without_error( jujus: List[jubilant.Juju], timeout: int = 60 * 45 ): From fecf0eea5fdc48311c28c015690970761205673f Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Thu, 21 May 2026 08:23:19 -0400 Subject: [PATCH 07/21] chore --- terraform/cos-dev/applications.tf | 14 ++-- terraform/cos-dev/integrations.tf | 38 ++++----- terraform/cos-dev/locals.tf | 2 +- terraform/cos-dev/offers.tf | 28 +++---- .../tests/conditional_ingress.tftest.hcl | 15 ++-- terraform/cos-dev/tests/mesh.tftest.hcl | 46 ++++++----- .../cos-dev/tests/reverse_proxy.tftest.hcl | 48 ++++++----- .../cos-dev/tests/revision_pin.tftest.hcl | 4 +- .../tests/topology_and_storage.tftest.hcl | 14 +--- terraform/cos-dev/variables.tf | 81 +++++++++---------- 10 files changed, 138 insertions(+), 152 deletions(-) diff --git a/terraform/cos-dev/applications.tf b/terraform/cos-dev/applications.tf index 304a3b8e..b69056f4 100644 --- a/terraform/cos-dev/applications.tf +++ b/terraform/cos-dev/applications.tf @@ -36,7 +36,7 @@ module "grafana" { } module "istio_ingress" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 source = "git::https://github.com/canonical/istio-ingress-k8s-operator//terraform" app_name = var.istio_ingress.app_name channel = local.channels.istio_ingress @@ -49,7 +49,7 @@ module "istio_ingress" { } module "istio_beacon" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 source = "git::https://github.com/canonical/istio-beacon-k8s-operator//terraform" app_name = var.istio_beacon.app_name channel = local.channels.istio_beacon @@ -235,7 +235,7 @@ module "seaweedfs" { } module "ssc" { - count = var.reverse_proxy.enabled ? 1 : 0 + count = var.internal_tls ? 1 : 0 source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform" app_name = var.ssc.app_name channel = local.channels.ssc @@ -366,7 +366,7 @@ module "tempo_worker_metrics_generator" { } module "traefik" { - count = var.reverse_proxy.enabled ? 1 : 0 + count = var.internal_tls ? 1 : 0 source = "git::https://github.com/canonical/traefik-k8s-operator//terraform" app_name = var.traefik.app_name channel = local.channels.traefik @@ -403,7 +403,7 @@ resource "juju_application" "s3_integrator_loki" { count = var.storage_backend == "s3" ? 1 : 0 config = merge({ endpoint = var.s3_endpoint - bucket = var.s3_buckets.loki + bucket = var.loki_bucket credentials = "secret:${juju_secret.loki_s3_credentials[0].secret_id}" }, var.s3_integrator.config) constraints = var.s3_integrator.constraints @@ -442,7 +442,7 @@ resource "juju_application" "s3_integrator_mimir" { count = var.storage_backend == "s3" ? 1 : 0 config = merge({ endpoint = var.s3_endpoint - bucket = var.s3_buckets.mimir + bucket = var.mimir_bucket credentials = "secret:${juju_secret.mimir_s3_credentials[0].secret_id}" }, var.s3_integrator.config) constraints = var.s3_integrator.constraints @@ -481,7 +481,7 @@ resource "juju_application" "s3_integrator_tempo" { count = var.storage_backend == "s3" ? 1 : 0 config = merge({ endpoint = var.s3_endpoint - bucket = var.s3_buckets.tempo + bucket = var.tempo_bucket credentials = "secret:${juju_secret.tempo_s3_credentials[0].secret_id}" }, var.s3_integrator.config) constraints = var.s3_integrator.constraints diff --git a/terraform/cos-dev/integrations.tf b/terraform/cos-dev/integrations.tf index 5aa32815..18947ae2 100644 --- a/terraform/cos-dev/integrations.tf +++ b/terraform/cos-dev/integrations.tf @@ -649,7 +649,7 @@ resource "juju_integration" "ingress" { app_name = module.mimir_coordinator.app_name endpoint = "ingress" } - } : k => v if var.ingress[k] && var.reverse_proxy.enabled + } : k => v if var.ingress[k] && var.internal_tls } model_uuid = var.model_uuid @@ -666,7 +666,7 @@ resource "juju_integration" "ingress" { } resource "juju_integration" "grafana_ingress" { - count = var.ingress.grafana && var.reverse_proxy.enabled ? 1 : 0 + count = var.ingress.grafana && var.internal_tls ? 1 : 0 model_uuid = var.model_uuid @@ -694,7 +694,7 @@ resource "juju_integration" "traefik_route" { app_name = module.tempo_coordinator.app_name endpoint = module.tempo_coordinator.requires.ingress } - } : k => v if var.ingress[k] && var.reverse_proxy.enabled + } : k => v if var.ingress[k] && var.internal_tls } model_uuid = var.model_uuid @@ -729,7 +729,7 @@ resource "juju_integration" "istio_ingress" { app_name = module.mimir_coordinator.app_name endpoint = "ingress" } - } : k => v if var.ingress[k] && var.mesh.enabled + } : k => v if var.ingress[k] && var.mesh_enabled } model_uuid = var.model_uuid @@ -746,7 +746,7 @@ resource "juju_integration" "istio_ingress" { } resource "juju_integration" "grafana_istio_ingress" { - count = var.ingress.grafana && var.mesh.enabled ? 1 : 0 + count = var.ingress.grafana && var.mesh_enabled ? 1 : 0 model_uuid = var.model_uuid @@ -774,7 +774,7 @@ resource "juju_integration" "istio_ingress_route" { app_name = module.tempo_coordinator.app_name endpoint = module.tempo_coordinator.requires.ingress } - } : k => v if var.ingress[k] && var.mesh.enabled + } : k => v if var.ingress[k] && var.mesh_enabled } model_uuid = var.model_uuid @@ -809,7 +809,7 @@ resource "juju_integration" "opentelemetry_collector_mimir_metrics" { # -------------- # Certificate Integrations -------------- resource "juju_integration" "internal_certificates" { - for_each = var.reverse_proxy.enabled ? { + for_each = var.internal_tls ? { alertmanager = { app_name = module.alertmanager.app_name endpoint = module.alertmanager.requires.certificates @@ -854,7 +854,7 @@ resource "juju_integration" "internal_certificates" { } resource "juju_integration" "traefik_receive_ca_certificate" { - count = var.reverse_proxy.enabled ? 1 : 0 + count = var.internal_tls ? 1 : 0 model_uuid = var.model_uuid application { @@ -871,12 +871,10 @@ resource "juju_integration" "traefik_receive_ca_certificate" { # -------------- # Provided by an external CA -------------- resource "juju_integration" "external_traefik_certificates" { - count = local.reverse_proxy_termination ? 1 : 0 + count = local.tls_termination ? 1 : 0 model_uuid = var.model_uuid - application { - offer_url = var.reverse_proxy.cmr_urls.certificates - } + application { offer_url = var.external_certificates_offer_url } application { name = module.traefik[0].app_name @@ -885,13 +883,10 @@ resource "juju_integration" "external_traefik_certificates" { } resource "juju_integration" "external_grafana_ca_cert" { - count = local.reverse_proxy_termination ? 1 : 0 + count = local.tls_termination ? 1 : 0 model_uuid = var.model_uuid - application { - offer_url = var.reverse_proxy.cmr_urls.receive_ca_cert - } - + application { offer_url = var.external_ca_cert_offer_url } application { name = module.grafana.app_name endpoint = module.grafana.requires.receive_ca_cert @@ -899,13 +894,10 @@ resource "juju_integration" "external_grafana_ca_cert" { } resource "juju_integration" "external_otelcol_ca_cert" { - count = local.reverse_proxy_termination ? 1 : 0 + count = local.tls_termination ? 1 : 0 model_uuid = var.model_uuid - application { - offer_url = var.reverse_proxy.cmr_urls.receive_ca_cert - } - + application { offer_url = var.external_ca_cert_offer_url } application { name = module.opentelemetry_collector.app_name endpoint = module.opentelemetry_collector.requires.receive_ca_cert @@ -945,7 +937,7 @@ resource "juju_integration" "traces_and_metrics_correlation" { # -------------- # Service Mesh --------------------- resource "juju_integration" "istio_beacon" { - for_each = var.mesh.enabled ? { + for_each = var.mesh_enabled ? { alertmanager = { app_name = module.alertmanager.app_name endpoint = module.alertmanager.requires.service_mesh diff --git a/terraform/cos-dev/locals.tf b/terraform/cos-dev/locals.tf index 14f75b58..f9f1c3b2 100644 --- a/terraform/cos-dev/locals.tf +++ b/terraform/cos-dev/locals.tf @@ -1,5 +1,5 @@ locals { - reverse_proxy_termination = var.reverse_proxy.enabled && var.reverse_proxy.cmr_urls.certificates != null ? true : false + tls_termination = var.external_certificates_offer_url != null ? true : false traefik_base = "ubuntu@20.04" tracks = { alertmanager = "dev" diff --git a/terraform/cos-dev/offers.tf b/terraform/cos-dev/offers.tf index 7ea4df9d..f925b10a 100644 --- a/terraform/cos-dev/offers.tf +++ b/terraform/cos-dev/offers.tf @@ -38,7 +38,7 @@ resource "juju_offer" "tempo_tracing" { # -------------- # CMR Mesh offers -------------- # resource "juju_offer" "alertmanager_provide_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "alertmanager-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.alertmanager.app_name @@ -46,7 +46,7 @@ resource "juju_offer" "alertmanager_provide_cmr_mesh" { } resource "juju_offer" "alertmanager_require_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "alertmanager-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.alertmanager.app_name @@ -54,7 +54,7 @@ resource "juju_offer" "alertmanager_require_cmr_mesh" { } resource "juju_offer" "catalogue_provide_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "catalogue-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.catalogue.app_name @@ -62,7 +62,7 @@ resource "juju_offer" "catalogue_provide_cmr_mesh" { } resource "juju_offer" "catalogue_require_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "catalogue-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.catalogue.app_name @@ -70,7 +70,7 @@ resource "juju_offer" "catalogue_require_cmr_mesh" { } resource "juju_offer" "grafana_provide_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "grafana-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.grafana.app_name @@ -80,7 +80,7 @@ resource "juju_offer" "grafana_provide_cmr_mesh" { } resource "juju_offer" "grafana_require_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "grafana-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.grafana.app_name @@ -90,7 +90,7 @@ resource "juju_offer" "grafana_require_cmr_mesh" { } resource "juju_offer" "loki_provide_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "loki-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.loki_coordinator.app_name @@ -98,7 +98,7 @@ resource "juju_offer" "loki_provide_cmr_mesh" { } resource "juju_offer" "loki_require_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "loki-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.loki_coordinator.app_name @@ -106,7 +106,7 @@ resource "juju_offer" "loki_require_cmr_mesh" { } resource "juju_offer" "mimir_provide_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "mimir-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.mimir_coordinator.app_name @@ -114,7 +114,7 @@ resource "juju_offer" "mimir_provide_cmr_mesh" { } resource "juju_offer" "mimir_require_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "mimir-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.mimir_coordinator.app_name @@ -122,7 +122,7 @@ resource "juju_offer" "mimir_require_cmr_mesh" { } resource "juju_offer" "opentelemetry_collector_provide_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "opentelemetry-collector-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.opentelemetry_collector.app_name @@ -130,7 +130,7 @@ resource "juju_offer" "opentelemetry_collector_provide_cmr_mesh" { } resource "juju_offer" "opentelemetry_collector_require_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "opentelemetry-collector-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.opentelemetry_collector.app_name @@ -138,7 +138,7 @@ resource "juju_offer" "opentelemetry_collector_require_cmr_mesh" { } resource "juju_offer" "tempo_provide_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "tempo-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.tempo_coordinator.app_name @@ -146,7 +146,7 @@ resource "juju_offer" "tempo_provide_cmr_mesh" { } resource "juju_offer" "tempo_require_cmr_mesh" { - count = var.mesh.enabled ? 1 : 0 + count = var.mesh_enabled ? 1 : 0 name = "tempo-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.tempo_coordinator.app_name diff --git a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl index faef5fbd..819bb44a 100644 --- a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl +++ b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl @@ -1,17 +1,14 @@ mock_provider "juju" {} -variables { - model_uuid = "00000000-0000-0000-0000-000000000000" -} +variables { model_uuid = "00000000-0000-0000-0000-000000000000" } -# TODO: This feature also depends on the x2 Traefik story, maybe reverse proxy is not the right name +# TODO: This feature also depends on the x2 Traefik story, maybe internal_tls is not the right name # TODO: Do we need to remove offers / outputs TF conditionally? # TODO: We need to keep the COS API the same across products: feature in COS, COS Lite, and COS Dev -# TODO: For the conditional ingress story, do we support the same components between mesh and reverse proxy? If yes, then they can share the ingress var. Otherwise not, e.g. COS Lite -# --- reverse proxy: enabled - all ingress disabled --- +# --- internal_tls: enabled - all ingress disabled --- -run "reverse_proxy_ingress_disabled" { +run "internal_tls_ingress_disabled" { command = plan variables { @@ -48,8 +45,8 @@ run "mesh_ingress_disabled" { command = plan variables { - mesh = { enabled = true } - reverse_proxy = { enabled = false } + mesh_enabled = true + internal_tls = false ingress = { alertmanager = false catalogue = false diff --git a/terraform/cos-dev/tests/mesh.tftest.hcl b/terraform/cos-dev/tests/mesh.tftest.hcl index 2ceba568..acc0bec3 100644 --- a/terraform/cos-dev/tests/mesh.tftest.hcl +++ b/terraform/cos-dev/tests/mesh.tftest.hcl @@ -1,30 +1,28 @@ mock_provider "juju" {} -variables { - model_uuid = "00000000-0000-0000-0000-000000000000" -} +variables { model_uuid = "00000000-0000-0000-0000-000000000000" } -# --- mesh and reverse_proxy enabled: validation error --- +# --- mesh and internal_tls enabled: validation error --- -run "mesh_and_reverse_proxy_enabled_fails" { +run "mesh_and_internal_tls_enabled_fails" { command = plan variables { - mesh = { enabled = true } - reverse_proxy = { enabled = true } + mesh_enabled = true + internal_tls = true } - expect_failures = [var.mesh.enabled] + expect_failures = [var.mesh_enabled] } -# --- mesh and reverse_proxy disabled: no validation error --- +# --- mesh and internal_tls disabled: no validation error --- -run "mesh_and_reverse_proxy_disabled" { +run "mesh_and_internal_tls_disabled" { command = plan variables { - mesh = { enabled = false } - reverse_proxy = { enabled = false } + mesh_enabled = false + internal_tls = false } } @@ -47,10 +45,15 @@ run "mesh_disabled" { condition = length(juju_integration.istio_beacon) == 0 error_message = "Expected no istio_beacon integrations when the mesh is disabled" } + + assert { + condition = length(juju_integration.istio_ingress) == 0 + error_message = "Expected no istio_ingress integrations when the mesh is disabled" + } assert { condition = length(juju_integration.grafana_istio_ingress) == 0 - error_message = "Expected no grafana_istio_beacon integrations when the mesh is disabled" + error_message = "Expected no grafana_istio_ingress integrations when the mesh is disabled" } assert { @@ -65,8 +68,8 @@ run "mesh_enabled" { command = plan variables { - mesh = { enabled = true } - reverse_proxy = { enabled = false } + mesh_enabled = true + internal_tls = false } assert { @@ -79,19 +82,24 @@ run "mesh_enabled" { error_message = "Expected istio_ingress module when the mesh is enabled" } + assert { + condition = length(juju_integration.istio_beacon) == 7 + error_message = "Unexpected istio_beacon integrations when the mesh is enabled" + } + assert { condition = length(juju_integration.istio_ingress) == 4 - error_message = "Expected 4 istio_ingress integrations (alertmanager, catalogue, loki, mimir), got ${length(juju_integration.istio_ingress)}" + error_message = "Unexpected istio_ingress integrations when the mesh is enabled" } - # Grafana uses a separate count-based resource due to lifecycle replace_triggered_by assert { condition = length(juju_integration.grafana_istio_ingress) == 1 - error_message = "Expected 1 grafana_istio_ingress integration, got ${length(juju_integration.grafana_istio_ingress)}" + error_message = "Unexpected grafana_istio_ingress integrations when the mesh is enabled" } assert { condition = length(juju_integration.istio_ingress_route) == 2 - error_message = "Expected 2 istio_ingress_route integrations (opentelemetry_collector, tempo), got ${length(juju_integration.istio_ingress_route)}" + error_message = "Unexpected istio_ingress_route integrations when the mesh is enabled" + } } diff --git a/terraform/cos-dev/tests/reverse_proxy.tftest.hcl b/terraform/cos-dev/tests/reverse_proxy.tftest.hcl index 357fd464..f6f49d75 100644 --- a/terraform/cos-dev/tests/reverse_proxy.tftest.hcl +++ b/terraform/cos-dev/tests/reverse_proxy.tftest.hcl @@ -1,63 +1,67 @@ mock_provider "juju" {} -variables { - model_uuid = "00000000-0000-0000-0000-000000000000" -} +variables { model_uuid = "00000000-0000-0000-0000-000000000000" } -# --- default: reverse proxy enabled - ingress via traefik --- +# --- default: internal_tls enabled - ingress via traefik --- -run "reverse_proxy_enabled" { +run "internal_tls_enabled" { command = plan + assert { + condition = length(module.ssc) == 1 + error_message = "Expected ssc module when internal_tls is enabled" + } + assert { condition = length(module.traefik) == 1 - error_message = "Expected traefik module when the reverse proxy is enabled" + error_message = "Expected traefik module when internal_tls is enabled" } assert { condition = length(juju_integration.ingress) == 4 - error_message = "Expected 4 ingress integrations (alertmanager, catalogue, loki, mimir), got ${length(juju_integration.ingress)}" + error_message = "Unexpected ingress integrations when internal_tls is enabled" } - # Grafana uses a separate count-based resource due to lifecycle replace_triggered_by assert { condition = length(juju_integration.grafana_ingress) == 1 - error_message = "Expected 1 grafana_ingress integration, got ${length(juju_integration.grafana_ingress)}" + error_message = "Unexpected grafana_ingress integrations when internal_tls is enabled" } assert { condition = length(juju_integration.traefik_route) == 2 - error_message = "Expected 2 traefik_route integrations (opentelemetry_collector, tempo), got ${length(juju_integration.traefik_route)}" + error_message = "Unexpected traefik_route integrations when internal_tls is enabled" } } -# --- reverse proxy disabled: no ingress via traefik --- +# --- internal_tls disabled: no ingress via traefik --- -run "reverse_proxy_disabled" { +run "internal_tls_disabled" { command = plan - variables { - mesh = { enabled = false } - reverse_proxy = { enabled = false } + variables { internal_tls = false } + + assert { + condition = length(module.ssc) == 0 + error_message = "Expected no self-signed-certificates module when internal_tls is disabled" } assert { condition = length(module.traefik) == 0 - error_message = "Expected no traefik module when the reverse proxy is disabled" + error_message = "Expected no traefik module when internal_tls is disabled" } assert { - condition = length(juju_integration.istio_ingress) == 0 - error_message = "Expected 0 istio_ingress integrations when the reverse proxy is disabled, got ${length(juju_integration.istio_ingress)}" + condition = length(juju_integration.ingress) == 0 + error_message = "Unexpected ingress integrations when internal_tls is disabled" } assert { - condition = length(juju_integration.grafana_istio_ingress) == 0 - error_message = "Expected 0 grafana_istio_ingress integrations when the reverse proxy is disabled" + condition = length(juju_integration.grafana_ingress) == 0 + error_message = "Unexpected grafana_ingress integrations when internal_tls is disabled" } assert { - condition = length(juju_integration.istio_ingress_route) == 0 - error_message = "Expected 0 istio_ingress_route integrations when the reverse proxy is disabled" + condition = length(juju_integration.traefik_route) == 0 + error_message = "Unexpected traefik_route integrations when internal_tls is disabled" } } diff --git a/terraform/cos-dev/tests/revision_pin.tftest.hcl b/terraform/cos-dev/tests/revision_pin.tftest.hcl index 7cf5f32e..6e68c9ac 100644 --- a/terraform/cos-dev/tests/revision_pin.tftest.hcl +++ b/terraform/cos-dev/tests/revision_pin.tftest.hcl @@ -1,8 +1,6 @@ mock_provider "juju" {} -variables { - model_uuid = "00000000-0000-0000-0000-000000000000" -} +variables { model_uuid = "00000000-0000-0000-0000-000000000000" } # --- User revision pin is respected and not overridden by juju_charm datasource --- diff --git a/terraform/cos-dev/tests/topology_and_storage.tftest.hcl b/terraform/cos-dev/tests/topology_and_storage.tftest.hcl index e29c3cdf..fd145fd6 100644 --- a/terraform/cos-dev/tests/topology_and_storage.tftest.hcl +++ b/terraform/cos-dev/tests/topology_and_storage.tftest.hcl @@ -1,8 +1,6 @@ mock_provider "juju" {} -variables { - model_uuid = "00000000-0000-0000-0000-000000000000" -} +variables { model_uuid = "00000000-0000-0000-0000-000000000000" } # --- Default: monolithic topology with seaweedfs --- @@ -214,11 +212,6 @@ run "monolithic_s3" { s3_endpoint = "https://s3.example.com" s3_access_key = "access-key" s3_secret_key = "secret-key" - s3_buckets = { - loki = "loki" - mimir = "mimir" - tempo = "tempo" - } } assert { @@ -292,11 +285,6 @@ run "distributed_s3" { s3_endpoint = "https://s3.example.com" s3_access_key = "access-key" s3_secret_key = "secret-key" - s3_buckets = { - loki = "loki" - mimir = "mimir" - tempo = "tempo" - } } assert { diff --git a/terraform/cos-dev/variables.tf b/terraform/cos-dev/variables.tf index 7e466ff9..a54905aa 100644 --- a/terraform/cos-dev/variables.tf +++ b/terraform/cos-dev/variables.tf @@ -48,49 +48,44 @@ variable "storage_backend" { # -------------- # Network configurations -------------- -variable "mesh" { +# TODO: If we name this one _enabled then we should also rename to internal_ca_enabled +variable "mesh_enabled" { description = "Configure the service mesh." - type = object({ - enabled = optional(bool, false) - cmr_urls = optional(object({ - alermanager = optional(string, null) - catalogue = optional(string, null) - grafana = optional(string, null) - loki = optional(string, null) - mimir = optional(string, null) - tempo = optional(string, null) - }), {} - ) - }) - default = {} + type = bool + default = false validation { - condition = !(var.mesh.enabled && var.reverse_proxy.enabled) + condition = !(var.internal_tls && var.mesh_enabled) error_message = "mesh_enabled and internal_tls cannot both be enabled at the same time." } } -variable "reverse_proxy" { - description = "Configure the reverse proxy." - type = object({ - enabled = optional(bool, true) - cmr_urls = optional(object({ - certificates = optional(string, null) - receive_ca_cert = optional(string, null) - }), {} - ) - }) - default = {} +variable "internal_tls" { + description = "Specify whether to use TLS or not for internal COS communication. By default, TLS is enabled using self-signed-certificates" + type = bool + default = true +} + +variable "external_certificates_offer_url" { + description = "A Juju offer URL of a CA providing the 'tls_certificates' integration for Traefik to supply it with server certificates" + type = string + default = null validation { condition = ( - (var.reverse_proxy.cmr_urls.certificates == null && var.reverse_proxy.cmr_urls.receive_ca_cert == null) || - (var.reverse_proxy.cmr_urls.certificates != null && var.reverse_proxy.cmr_urls.receive_ca_cert != null) + (var.external_certificates_offer_url == null && var.external_ca_cert_offer_url == null) || + (var.external_certificates_offer_url != null && var.external_ca_cert_offer_url != null) ) - error_message = "CMRs for certificates and receive_ca_cert must be supplied together (either both set or both null)." + error_message = "external_certificates_offer_url and external_ca_cert_offer_url must be supplied together (either both set or both null)." } } +variable "external_ca_cert_offer_url" { + description = "A Juju offer URL (e.g. admin/external-ca.send-ca-cert) of a CA providing the 'certificate_transfer' integration for applications to trust ingress via Traefik." + type = string + default = null +} + # -------------- # Ingress configurations -------------- variable "ingress" { @@ -129,18 +124,22 @@ variable "s3_secret_key" { default = null } -# TODO: Move the refactor efforts into a separate PR that the mesh team can branch from to get mesh merged: -# 1. buckets, reverse_proxy -# 2. I couldn't group s3_ because this has "sensitive" vars. Check if this is 100% not possible. -# What if we made the whole s3 var sensitive? -variable "s3_buckets" { - description = "S3 bucket names for components" - type = object({ - loki = optional(string, "loki") - mimir = optional(string, "mimir") - tempo = optional(string, "tempo") - }) - default = {} +variable "loki_bucket" { + description = "Loki S3 bucket name" + type = string + default = "loki" +} + +variable "mimir_bucket" { + description = "Mimir S3 bucket name" + type = string + default = "mimir" +} + +variable "tempo_bucket" { + description = "Tempo S3 bucket name" + type = string + default = "tempo" } # -------------- # Application configurations -------------- From fccec76f64eb05dfb58223c8b573f6c6193df0b9 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Thu, 21 May 2026 08:35:48 -0400 Subject: [PATCH 08/21] chore --- terraform/cos-dev/README.md | 10 +++++++--- terraform/cos-dev/locals.tf | 2 +- terraform/cos-dev/tests/mesh.tftest.hcl | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/terraform/cos-dev/README.md b/terraform/cos-dev/README.md index 9ff8b7be..8a5d76d5 100644 --- a/terraform/cos-dev/README.md +++ b/terraform/cos-dev/README.md @@ -51,27 +51,31 @@ This module is intended for development and testing environments where full HA i | [alertmanager](#input\_alertmanager) | Application configuration for Alertmanager. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "alertmanager")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [base](#input\_base) | The operating system on which to deploy. E.g. ubuntu@24.04. Check Charmhub for per-charm base support. | `string` | `"ubuntu@24.04"` | no | | [catalogue](#input\_catalogue) | Application configuration for Catalogue. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "catalogue")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | +| [external\_ca\_cert\_offer\_url](#input\_external\_ca\_cert\_offer\_url) | A Juju offer URL (e.g. admin/external-ca.send-ca-cert) of a CA providing the 'certificate\_transfer' integration for applications to trust ingress via Traefik. | `string` | `null` | no | +| [external\_certificates\_offer\_url](#input\_external\_certificates\_offer\_url) | A Juju offer URL of a CA providing the 'tls\_certificates' integration for Traefik to supply it with server certificates | `string` | `null` | no | | [grafana](#input\_grafana) | Application configuration for Grafana. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "grafana")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [ingress](#input\_ingress) | Per-component toggle for ingress integrations |
object({
alertmanager = optional(bool, true)
catalogue = optional(bool, true)
grafana = optional(bool, true)
loki = optional(bool, true)
mimir = optional(bool, true)
opentelemetry_collector = optional(bool, true)
tempo = optional(bool, true)
})
| `{}` | no | +| [internal\_tls](#input\_internal\_tls) | Specify whether to use TLS or not for internal COS communication. By default, TLS is enabled using self-signed-certificates | `bool` | `true` | no | | [istio\_beacon](#input\_istio\_beacon) | Application configuration for istio-beacon. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "istio-beacon")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [istio\_ingress](#input\_istio\_ingress) | Application configuration for istio-ingress. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "istio-ingress")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | +| [loki\_bucket](#input\_loki\_bucket) | Loki S3 bucket name | `string` | `"loki"` | no | | [loki\_coordinator](#input\_loki\_coordinator) | Application configuration for the Loki coordinator. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "loki")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [loki\_worker](#input\_loki\_worker) | Application configuration for the Loki worker(s). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "loki-worker")
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
# Monolithic mode (role-all)
config = optional(map(string), {})
storage_directives = optional(map(string), {})
units = optional(number, 1)
# Distributed mode
backend_config = optional(map(string), {})
read_config = optional(map(string), {})
write_config = optional(map(string), {})
backend_storage_directives = optional(map(string), {})
read_storage_directives = optional(map(string), {})
write_storage_directives = optional(map(string), {})
backend_units = optional(number, 1)
read_units = optional(number, 1)
write_units = optional(number, 1)
})
| `{}` | no | -| [mesh](#input\_mesh) | Configure the service mesh. |
object({
enabled = optional(bool, false)
cmr_urls = optional(object({
alermanager = optional(string, null)
catalogue = optional(string, null)
grafana = optional(string, null)
loki = optional(string, null)
mimir = optional(string, null)
tempo = optional(string, null)
}), {}
)
})
| `{}` | no | +| [mesh\_enabled](#input\_mesh\_enabled) | Configure the service mesh. | `bool` | `false` | no | +| [mimir\_bucket](#input\_mimir\_bucket) | Mimir S3 bucket name | `string` | `"mimir"` | no | | [mimir\_coordinator](#input\_mimir\_coordinator) | Application configuration for the Mimir coordinator. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "mimir")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [mimir\_worker](#input\_mimir\_worker) | Application configuration for the Mimir worker(s). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "mimir-worker")
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
# Monolithic mode (role-all)
config = optional(map(string), {})
storage_directives = optional(map(string), {})
units = optional(number, 1)
# Distributed mode
backend_config = optional(map(string), {})
read_config = optional(map(string), {})
write_config = optional(map(string), {})
backend_storage_directives = optional(map(string), {})
read_storage_directives = optional(map(string), {})
write_storage_directives = optional(map(string), {})
backend_units = optional(number, 1)
read_units = optional(number, 1)
write_units = optional(number, 1)
})
| `{}` | no | | [model\_uuid](#input\_model\_uuid) | Reference to an existing model resource or data source for the model to deploy to | `string` | n/a | yes | | [opentelemetry\_collector](#input\_opentelemetry\_collector) | Application configuration for OpenTelemetry Collector. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "otelcol")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | -| [reverse\_proxy](#input\_reverse\_proxy) | Configure the reverse proxy. |
object({
enabled = optional(bool, true)
cmr_urls = optional(object({
certificates = optional(string, null)
receive_ca_cert = optional(string, null)
}), {}
)
})
| `{}` | no | | [risk](#input\_risk) | Risk level that the applications are (unless overwritten by individual channels) deployed from | `string` | `"edge"` | no | | [s3\_access\_key](#input\_s3\_access\_key) | S3 access-key credential. Required when storage\_backend is 's3'. | `string` | `null` | no | -| [s3\_buckets](#input\_s3\_buckets) | S3 bucket names for components |
object({
loki = optional(string, "loki")
mimir = optional(string, "mimir")
tempo = optional(string, "tempo")
})
| `{}` | no | | [s3\_endpoint](#input\_s3\_endpoint) | S3 endpoint URL. Required when storage\_backend is 's3'. | `string` | `null` | no | | [s3\_integrator](#input\_s3\_integrator) | Application configuration shared by all S3-integrators (one deployed per coordinated worker). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [s3\_secret\_key](#input\_s3\_secret\_key) | S3 secret-key credential. Required when storage\_backend is 's3'. | `string` | `null` | no | | [seaweedfs](#input\_seaweedfs) | Application configuration for SeaweedFS. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "seaweedfs")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [ssc](#input\_ssc) | Application configuration for Self-signed-certificates. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "ca")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [storage\_backend](#input\_storage\_backend) | Storage backend: 'seaweedfs' (built-in S3-compatible storage) or 's3' (external S3/Ceph via s3-integrator). | `string` | `"seaweedfs"` | no | +| [tempo\_bucket](#input\_tempo\_bucket) | Tempo S3 bucket name | `string` | `"tempo"` | no | | [tempo\_coordinator](#input\_tempo\_coordinator) | Application configuration for the Tempo coordinator. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "tempo")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [tempo\_worker](#input\_tempo\_worker) | Application configuration for the Tempo worker(s). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "tempo-worker")
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
# Monolithic mode (role-all)
config = optional(map(string), {})
storage_directives = optional(map(string), {})
units = optional(number, 1)
# Distributed mode
querier_config = optional(map(string), {})
query_frontend_config = optional(map(string), {})
ingester_config = optional(map(string), {})
distributor_config = optional(map(string), {})
compactor_config = optional(map(string), {})
metrics_generator_config = optional(map(string), {})
querier_storage_directives = optional(map(string), {})
query_frontend_storage_directives = optional(map(string), {})
ingester_storage_directives = optional(map(string), {})
distributor_storage_directives = optional(map(string), {})
compactor_storage_directives = optional(map(string), {})
metrics_generator_storage_directives = optional(map(string), {})
querier_units = optional(number, 1)
query_frontend_units = optional(number, 1)
ingester_units = optional(number, 1)
distributor_units = optional(number, 1)
compactor_units = optional(number, 1)
metrics_generator_units = optional(number, 1)
})
| `{}` | no | | [topology](#input\_topology) | Deployment topology: 'monolithic' (single role-all worker per component) or 'distributed' (separate workers per role). | `string` | `"monolithic"` | no | diff --git a/terraform/cos-dev/locals.tf b/terraform/cos-dev/locals.tf index f9f1c3b2..878d6ef2 100644 --- a/terraform/cos-dev/locals.tf +++ b/terraform/cos-dev/locals.tf @@ -1,6 +1,6 @@ locals { tls_termination = var.external_certificates_offer_url != null ? true : false - traefik_base = "ubuntu@20.04" + traefik_base = "ubuntu@20.04" tracks = { alertmanager = "dev" catalogue = "dev" diff --git a/terraform/cos-dev/tests/mesh.tftest.hcl b/terraform/cos-dev/tests/mesh.tftest.hcl index acc0bec3..33bc4267 100644 --- a/terraform/cos-dev/tests/mesh.tftest.hcl +++ b/terraform/cos-dev/tests/mesh.tftest.hcl @@ -45,7 +45,7 @@ run "mesh_disabled" { condition = length(juju_integration.istio_beacon) == 0 error_message = "Expected no istio_beacon integrations when the mesh is disabled" } - + assert { condition = length(juju_integration.istio_ingress) == 0 error_message = "Expected no istio_ingress integrations when the mesh is disabled" From d91bee9f428c69f7facb7799343f4b5c1cd69710 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Thu, 21 May 2026 12:06:23 -0400 Subject: [PATCH 09/21] chore --- terraform/cos-dev/applications.tf | 4 +- terraform/cos-dev/integrations.tf | 18 ++- terraform/cos-dev/locals.tf | 3 + .../tests/conditional_ingress.tftest.hcl | 110 ++++++++++++++++-- .../cos-dev/tests/input_validation.tftest.hcl | 75 ++++++++++++ .../cos-dev/tests/internal_tls.tftest.hcl | 47 ++++++++ terraform/cos-dev/tests/mesh.tftest.hcl | 24 ---- .../cos-dev/tests/reverse_proxy.tftest.hcl | 67 ----------- 8 files changed, 234 insertions(+), 114 deletions(-) create mode 100644 terraform/cos-dev/tests/input_validation.tftest.hcl create mode 100644 terraform/cos-dev/tests/internal_tls.tftest.hcl delete mode 100644 terraform/cos-dev/tests/reverse_proxy.tftest.hcl diff --git a/terraform/cos-dev/applications.tf b/terraform/cos-dev/applications.tf index b69056f4..0f011f2e 100644 --- a/terraform/cos-dev/applications.tf +++ b/terraform/cos-dev/applications.tf @@ -36,7 +36,7 @@ module "grafana" { } module "istio_ingress" { - count = var.mesh_enabled ? 1 : 0 + count = local.istio_ingress_enabled ? 1 : 0 source = "git::https://github.com/canonical/istio-ingress-k8s-operator//terraform" app_name = var.istio_ingress.app_name channel = local.channels.istio_ingress @@ -366,7 +366,7 @@ module "tempo_worker_metrics_generator" { } module "traefik" { - count = var.internal_tls ? 1 : 0 + count = local.traefik_enabled ? 1 : 0 source = "git::https://github.com/canonical/traefik-k8s-operator//terraform" app_name = var.traefik.app_name channel = local.channels.traefik diff --git a/terraform/cos-dev/integrations.tf b/terraform/cos-dev/integrations.tf index 18947ae2..7aab1d4a 100644 --- a/terraform/cos-dev/integrations.tf +++ b/terraform/cos-dev/integrations.tf @@ -649,7 +649,7 @@ resource "juju_integration" "ingress" { app_name = module.mimir_coordinator.app_name endpoint = "ingress" } - } : k => v if var.ingress[k] && var.internal_tls + } : k => v if local.traefik_enabled && var.ingress[k] } model_uuid = var.model_uuid @@ -666,7 +666,7 @@ resource "juju_integration" "ingress" { } resource "juju_integration" "grafana_ingress" { - count = var.ingress.grafana && var.internal_tls ? 1 : 0 + count = local.traefik_enabled && var.ingress.grafana ? 1 : 0 model_uuid = var.model_uuid @@ -694,7 +694,7 @@ resource "juju_integration" "traefik_route" { app_name = module.tempo_coordinator.app_name endpoint = module.tempo_coordinator.requires.ingress } - } : k => v if var.ingress[k] && var.internal_tls + } : k => v if local.traefik_enabled && var.ingress[k] } model_uuid = var.model_uuid @@ -729,7 +729,7 @@ resource "juju_integration" "istio_ingress" { app_name = module.mimir_coordinator.app_name endpoint = "ingress" } - } : k => v if var.ingress[k] && var.mesh_enabled + } : k => v if local.istio_ingress_enabled && var.ingress[k] } model_uuid = var.model_uuid @@ -746,7 +746,7 @@ resource "juju_integration" "istio_ingress" { } resource "juju_integration" "grafana_istio_ingress" { - count = var.ingress.grafana && var.mesh_enabled ? 1 : 0 + count = local.istio_ingress_enabled && var.ingress.grafana ? 1 : 0 model_uuid = var.model_uuid @@ -774,7 +774,7 @@ resource "juju_integration" "istio_ingress_route" { app_name = module.tempo_coordinator.app_name endpoint = module.tempo_coordinator.requires.ingress } - } : k => v if var.ingress[k] && var.mesh_enabled + } : k => v if local.istio_ingress_enabled && var.ingress[k] } model_uuid = var.model_uuid @@ -854,7 +854,7 @@ resource "juju_integration" "internal_certificates" { } resource "juju_integration" "traefik_receive_ca_certificate" { - count = var.internal_tls ? 1 : 0 + count = local.traefik_enabled && var.internal_tls ? 1 : 0 model_uuid = var.model_uuid application { @@ -871,11 +871,10 @@ resource "juju_integration" "traefik_receive_ca_certificate" { # -------------- # Provided by an external CA -------------- resource "juju_integration" "external_traefik_certificates" { - count = local.tls_termination ? 1 : 0 + count = local.traefik_enabled && local.tls_termination ? 1 : 0 model_uuid = var.model_uuid application { offer_url = var.external_certificates_offer_url } - application { name = module.traefik[0].app_name endpoint = module.traefik[0].endpoints.certificates @@ -980,4 +979,3 @@ resource "juju_integration" "istio_beacon" { endpoint = each.value.endpoint } } - diff --git a/terraform/cos-dev/locals.tf b/terraform/cos-dev/locals.tf index 878d6ef2..a59766dc 100644 --- a/terraform/cos-dev/locals.tf +++ b/terraform/cos-dev/locals.tf @@ -1,5 +1,8 @@ locals { tls_termination = var.external_certificates_offer_url != null ? true : false + reverse_proxy_enabled = anytrue(values(var.ingress)) + traefik_enabled = local.reverse_proxy_enabled && !var.mesh_enabled + istio_ingress_enabled = local.reverse_proxy_enabled && var.mesh_enabled traefik_base = "ubuntu@20.04" tracks = { alertmanager = "dev" diff --git a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl index 819bb44a..83280b41 100644 --- a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl +++ b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl @@ -6,9 +6,9 @@ variables { model_uuid = "00000000-0000-0000-0000-000000000000" } # TODO: Do we need to remove offers / outputs TF conditionally? # TODO: We need to keep the COS API the same across products: feature in COS, COS Lite, and COS Dev -# --- internal_tls: enabled - all ingress disabled --- +# --- traefik: all ingress disabled --- -run "internal_tls_ingress_disabled" { +run "traefik_ingress_disabled" { command = plan variables { @@ -23,30 +23,35 @@ run "internal_tls_ingress_disabled" { } } + assert { + condition = length(module.traefik) == 0 + error_message = "Expected no traefik module when ingress is disabled" + } + assert { condition = length(juju_integration.ingress) == 0 - error_message = "Expected 0 ingress integrations, got ${length(juju_integration.ingress)}" + error_message = "Unexpected ingress integrations when ingress is disabled" } assert { condition = length(juju_integration.grafana_ingress) == 0 - error_message = "Expected 0 grafana_ingress integrations, got ${length(juju_integration.grafana_ingress)}" + error_message = "Unexpected grafana_ingress integrations when ingress is disabled" } assert { condition = length(juju_integration.traefik_route) == 0 - error_message = "Expected 0 traefik_route integrations, got ${length(juju_integration.traefik_route)}" + error_message = "Unexpected traefik_route integrations when ingress is disabled" } } -# --- mesh: enabled - all ingress disabled --- +# --- istio: all ingress disabled --- -run "mesh_ingress_disabled" { +run "mesh_enabled_ingress_disabled" { command = plan variables { - mesh_enabled = true internal_tls = false + mesh_enabled = true ingress = { alertmanager = false catalogue = false @@ -58,18 +63,101 @@ run "mesh_ingress_disabled" { } } + assert { + condition = length(module.istio_ingress) == 0 + error_message = "Expected no istio_ingress module when ingress is disabled" + } + assert { condition = length(juju_integration.istio_ingress) == 0 - error_message = "Expected 0 istio_ingress integrations, got ${length(juju_integration.istio_ingress)}" + error_message = "Unexpected istio_ingress integrations when ingress is disabled" } assert { condition = length(juju_integration.grafana_istio_ingress) == 0 - error_message = "Expected 0 grafana_istio_ingress integrations, got ${length(juju_integration.grafana_istio_ingress)}" + error_message = "Unexpected grafana_istio_ingress integrations when ingress is disabled" } assert { condition = length(juju_integration.istio_ingress_route) == 0 - error_message = "Expected 0 istio_ingress_route integrations, got ${length(juju_integration.istio_ingress_route)}" + error_message = "Unexpected istio_ingress_route integrations when ingress is disabled" + } +} + +# --- traefik: some ingress enabled --- + +run "traefik_ingress_enabled" { + command = plan + + variables { + ingress = { + alertmanager = false + catalogue = true + grafana = false + loki = true + mimir = false + opentelemetry_collector = true + tempo = false + } + } + + assert { + condition = length(module.traefik) == 1 + error_message = "Expected a traefik module when ingress is enabled" + } + + assert { + condition = length(juju_integration.ingress) == 2 + error_message = "Unexpected ingress integrations when ingress is enabled" + } + + assert { + condition = length(juju_integration.grafana_ingress) == 0 + error_message = "Unexpected grafana_ingress integrations when ingress is enabled" + } + + assert { + condition = length(juju_integration.traefik_route) == 1 + error_message = "Unexpected traefik_route integrations when ingress is enabled" + } +} + +# --- istio: some ingress enabled --- + +run "mesh_enabled_ingress_enabled" { + command = plan + + variables { + internal_tls = false + mesh_enabled = true + ingress = { + alertmanager = false + catalogue = true + grafana = false + loki = true + mimir = false + opentelemetry_collector = true + tempo = false + } + } + + assert { + condition = length(module.istio_ingress) == 1 + error_message = "Expected an istio_ingress module when ingress is enabled" + } + + assert { + condition = length(juju_integration.istio_ingress) == 2 + error_message = "Unexpected istio_ingress integrations when ingress is enabled" + } + + assert { + condition = length(juju_integration.grafana_istio_ingress) == 0 + error_message = "Unexpected grafana_istio_ingress integrations when ingress is enabled" + } + + assert { + condition = length(juju_integration.istio_ingress_route) == 1 + error_message = "Unexpected istio_ingress_route integrations when ingress is enabled" } } diff --git a/terraform/cos-dev/tests/input_validation.tftest.hcl b/terraform/cos-dev/tests/input_validation.tftest.hcl new file mode 100644 index 00000000..81f7f5d9 --- /dev/null +++ b/terraform/cos-dev/tests/input_validation.tftest.hcl @@ -0,0 +1,75 @@ +mock_provider "juju" {} + +variables { model_uuid = "00000000-0000-0000-0000-000000000000" } + +# --- mesh and internal_tls disabled: no validation error --- + +run "mesh_and_internal_tls_disabled" { + command = plan + + variables { + mesh_enabled = false + internal_tls = false + } +} + +# --- mesh and internal_tls enabled: validation error --- + +run "mesh_and_internal_tls_enabled_fails" { + command = plan + + variables { + mesh_enabled = true + internal_tls = true + } + + expect_failures = [var.mesh_enabled] +} + +# --- external cert URLs both null: no validation error --- + +run "external_cert_urls_both_null" { + command = plan + + variables { + external_certificates_offer_url = null + external_ca_cert_offer_url = null + } +} + +# --- external cert URLs both set: no validation error --- + +run "external_cert_urls_both_set" { + command = plan + + variables { + external_certificates_offer_url = "admin/external-ca.tls-certificates" + external_ca_cert_offer_url = "admin/external-ca.send-ca-cert" + } +} + +# --- only external_certificates_offer_url set: validation error --- + +run "external_certificates_offer_url_only_fails" { + command = plan + + variables { + external_certificates_offer_url = "admin/external-ca.tls-certificates" + external_ca_cert_offer_url = null + } + + expect_failures = [var.external_certificates_offer_url] +} + +# --- only external_ca_cert_offer_url set: validation error --- + +run "external_ca_cert_offer_url_only_fails" { + command = plan + + variables { + external_certificates_offer_url = null + external_ca_cert_offer_url = "admin/external-ca.send-ca-cert" + } + + expect_failures = [var.external_certificates_offer_url] +} diff --git a/terraform/cos-dev/tests/internal_tls.tftest.hcl b/terraform/cos-dev/tests/internal_tls.tftest.hcl new file mode 100644 index 00000000..df0d243b --- /dev/null +++ b/terraform/cos-dev/tests/internal_tls.tftest.hcl @@ -0,0 +1,47 @@ +mock_provider "juju" {} + +variables { model_uuid = "00000000-0000-0000-0000-000000000000" } + +# --- default: internal_tls enabled --- + +run "internal_tls_enabled" { + command = plan + + assert { + condition = length(module.ssc) == 1 + error_message = "Expected ssc module when internal_tls is enabled" + } + + assert { + condition = length(juju_integration.internal_certificates) == 7 + error_message = "Unexpected internal_certificates integrations when internal_tls is enabled" + } + + assert { + condition = length(juju_integration.traefik_receive_ca_certificate) == 1 + error_message = "Unexpected traefik_receive_ca_certificate integrations when internal_tls is enabled" + } +} + +# --- internal_tls disabled: no ingress via traefik --- + +run "internal_tls_disabled" { + command = plan + + variables { internal_tls = false } + + assert { + condition = length(module.ssc) == 0 + error_message = "Expected no self-signed-certificates module when internal_tls is disabled" + } + + assert { + condition = length(juju_integration.internal_certificates) == 0 + error_message = "Unexpected internal_certificates integrations when internal_tls is disabled" + } + + assert { + condition = length(juju_integration.traefik_receive_ca_certificate) == 0 + error_message = "Unexpected traefik_receive_ca_certificate integrations when internal_tls is disabled" + } +} diff --git a/terraform/cos-dev/tests/mesh.tftest.hcl b/terraform/cos-dev/tests/mesh.tftest.hcl index 33bc4267..2e080b5a 100644 --- a/terraform/cos-dev/tests/mesh.tftest.hcl +++ b/terraform/cos-dev/tests/mesh.tftest.hcl @@ -2,30 +2,6 @@ mock_provider "juju" {} variables { model_uuid = "00000000-0000-0000-0000-000000000000" } -# --- mesh and internal_tls enabled: validation error --- - -run "mesh_and_internal_tls_enabled_fails" { - command = plan - - variables { - mesh_enabled = true - internal_tls = true - } - - expect_failures = [var.mesh_enabled] -} - -# --- mesh and internal_tls disabled: no validation error --- - -run "mesh_and_internal_tls_disabled" { - command = plan - - variables { - mesh_enabled = false - internal_tls = false - } -} - # --- default: mesh disabled - no ingress via istio --- run "mesh_disabled" { diff --git a/terraform/cos-dev/tests/reverse_proxy.tftest.hcl b/terraform/cos-dev/tests/reverse_proxy.tftest.hcl deleted file mode 100644 index f6f49d75..00000000 --- a/terraform/cos-dev/tests/reverse_proxy.tftest.hcl +++ /dev/null @@ -1,67 +0,0 @@ -mock_provider "juju" {} - -variables { model_uuid = "00000000-0000-0000-0000-000000000000" } - -# --- default: internal_tls enabled - ingress via traefik --- - -run "internal_tls_enabled" { - command = plan - - assert { - condition = length(module.ssc) == 1 - error_message = "Expected ssc module when internal_tls is enabled" - } - - assert { - condition = length(module.traefik) == 1 - error_message = "Expected traefik module when internal_tls is enabled" - } - - assert { - condition = length(juju_integration.ingress) == 4 - error_message = "Unexpected ingress integrations when internal_tls is enabled" - } - - assert { - condition = length(juju_integration.grafana_ingress) == 1 - error_message = "Unexpected grafana_ingress integrations when internal_tls is enabled" - } - - assert { - condition = length(juju_integration.traefik_route) == 2 - error_message = "Unexpected traefik_route integrations when internal_tls is enabled" - } -} - -# --- internal_tls disabled: no ingress via traefik --- - -run "internal_tls_disabled" { - command = plan - - variables { internal_tls = false } - - assert { - condition = length(module.ssc) == 0 - error_message = "Expected no self-signed-certificates module when internal_tls is disabled" - } - - assert { - condition = length(module.traefik) == 0 - error_message = "Expected no traefik module when internal_tls is disabled" - } - - assert { - condition = length(juju_integration.ingress) == 0 - error_message = "Unexpected ingress integrations when internal_tls is disabled" - } - - assert { - condition = length(juju_integration.grafana_ingress) == 0 - error_message = "Unexpected grafana_ingress integrations when internal_tls is disabled" - } - - assert { - condition = length(juju_integration.traefik_route) == 0 - error_message = "Unexpected traefik_route integrations when internal_tls is disabled" - } -} From bfffa490a528c992d4bf462a4fcd0f871c3a00e4 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Thu, 21 May 2026 12:15:04 -0400 Subject: [PATCH 10/21] refactor: Traefik and SSC are conditional --- terraform/cos-dev/applications.tf | 243 +++++++++--------- terraform/cos-dev/integrations.tf | 46 ++-- terraform/cos-dev/locals.tf | 2 + .../tests/conditional_ingress.tftest.hcl | 131 ++-------- .../cos-dev/tests/input_validation.tftest.hcl | 47 ++++ .../cos-dev/tests/internal_tls.tftest.hcl | 47 ++++ terraform/cos-dev/variables.tf | 26 +- 7 files changed, 273 insertions(+), 269 deletions(-) create mode 100644 terraform/cos-dev/tests/input_validation.tftest.hcl create mode 100644 terraform/cos-dev/tests/internal_tls.tftest.hcl diff --git a/terraform/cos-dev/applications.tf b/terraform/cos-dev/applications.tf index b76149c0..e0857be3 100644 --- a/terraform/cos-dev/applications.tf +++ b/terraform/cos-dev/applications.tf @@ -195,8 +195,6 @@ module "opentelemetry_collector" { units = var.opentelemetry_collector.units } -# -------------- # SeaweedFS (storage_backend = "seaweedfs") -------------- - module "seaweedfs" { count = var.storage_backend == "seaweedfs" ? 1 : 0 source = "git::https://github.com/canonical/observability-stack//terraform/seaweedfs" @@ -210,126 +208,6 @@ module "seaweedfs" { units = var.seaweedfs.units } -# -------------- # S3-integrators (storage_backend = "s3") -------------- - -resource "juju_secret" "loki_s3_credentials" { - count = var.storage_backend == "s3" ? 1 : 0 - model_uuid = var.model_uuid - name = "loki-s3-credentials" - value = { - access-key = var.s3_access_key - secret-key = var.s3_secret_key - } - info = "S3 credentials for Loki" -} - -resource "juju_access_secret" "loki_s3_credentials_access" { - count = var.storage_backend == "s3" ? 1 : 0 - model_uuid = var.model_uuid - applications = [juju_application.s3_integrator_loki[0].name] - secret_id = juju_secret.loki_s3_credentials[0].secret_id -} - -# TODO: Replace with a remote terraform module once the s3-integrator charm exposes one. -resource "juju_application" "s3_integrator_loki" { - count = var.storage_backend == "s3" ? 1 : 0 - config = merge({ - endpoint = var.s3_endpoint - bucket = var.loki_bucket - credentials = "secret:${juju_secret.loki_s3_credentials[0].secret_id}" - }, var.s3_integrator.config) - constraints = var.s3_integrator.constraints - model_uuid = var.model_uuid - name = "${var.loki_coordinator.app_name}-s3-integrator" - storage_directives = var.s3_integrator.storage_directives - trust = true - units = var.s3_integrator.units - - charm { - name = "s3-integrator" - channel = local.channels.s3_integrator - revision = local.revisions.s3_integrator - } -} - -resource "juju_secret" "mimir_s3_credentials" { - count = var.storage_backend == "s3" ? 1 : 0 - model_uuid = var.model_uuid - name = "mimir-s3-credentials" - value = { - access-key = var.s3_access_key - secret-key = var.s3_secret_key - } - info = "S3 credentials for Mimir" -} - -resource "juju_access_secret" "mimir_s3_credentials_access" { - count = var.storage_backend == "s3" ? 1 : 0 - model_uuid = var.model_uuid - applications = [juju_application.s3_integrator_mimir[0].name] - secret_id = juju_secret.mimir_s3_credentials[0].secret_id -} - -resource "juju_application" "s3_integrator_mimir" { - count = var.storage_backend == "s3" ? 1 : 0 - config = merge({ - endpoint = var.s3_endpoint - bucket = var.mimir_bucket - credentials = "secret:${juju_secret.mimir_s3_credentials[0].secret_id}" - }, var.s3_integrator.config) - constraints = var.s3_integrator.constraints - model_uuid = var.model_uuid - name = "${var.mimir_coordinator.app_name}-s3-integrator" - storage_directives = var.s3_integrator.storage_directives - trust = true - units = var.s3_integrator.units - - charm { - name = "s3-integrator" - channel = local.channels.s3_integrator - revision = local.revisions.s3_integrator - } -} - -resource "juju_secret" "tempo_s3_credentials" { - count = var.storage_backend == "s3" ? 1 : 0 - model_uuid = var.model_uuid - name = "tempo-s3-credentials" - value = { - access-key = var.s3_access_key - secret-key = var.s3_secret_key - } - info = "S3 credentials for Tempo" -} - -resource "juju_access_secret" "tempo_s3_credentials_access" { - count = var.storage_backend == "s3" ? 1 : 0 - model_uuid = var.model_uuid - applications = [juju_application.s3_integrator_tempo[0].name] - secret_id = juju_secret.tempo_s3_credentials[0].secret_id -} - -resource "juju_application" "s3_integrator_tempo" { - count = var.storage_backend == "s3" ? 1 : 0 - config = merge({ - endpoint = var.s3_endpoint - bucket = var.tempo_bucket - credentials = "secret:${juju_secret.tempo_s3_credentials[0].secret_id}" - }, var.s3_integrator.config) - constraints = var.s3_integrator.constraints - model_uuid = var.model_uuid - name = "${var.tempo_coordinator.app_name}-s3-integrator" - storage_directives = var.s3_integrator.storage_directives - trust = true - units = var.s3_integrator.units - - charm { - name = "s3-integrator" - channel = local.channels.s3_integrator - revision = local.revisions.s3_integrator - } -} - module "ssc" { count = var.internal_tls ? 1 : 0 source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform" @@ -462,6 +340,7 @@ module "tempo_worker_metrics_generator" { } module "traefik" { + count = local.traefik_enabled ? 1 : 0 source = "git::https://github.com/canonical/traefik-k8s-operator//terraform" app_name = var.traefik.app_name channel = local.channels.traefik @@ -472,3 +351,123 @@ module "traefik" { storage_directives = var.traefik.storage_directives units = var.traefik.units } + +# -------------- # S3-integrator resources (storage_backend = "s3") -------------- + +resource "juju_secret" "loki_s3_credentials" { + count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid + name = "loki-s3-credentials" + value = { + access-key = var.s3_access_key + secret-key = var.s3_secret_key + } + info = "S3 credentials for Loki" +} + +resource "juju_access_secret" "loki_s3_credentials_access" { + count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid + applications = [juju_application.s3_integrator_loki[0].name] + secret_id = juju_secret.loki_s3_credentials[0].secret_id +} + +# TODO: Replace with a remote terraform module once the s3-integrator charm exposes one. +resource "juju_application" "s3_integrator_loki" { + count = var.storage_backend == "s3" ? 1 : 0 + config = merge({ + endpoint = var.s3_endpoint + bucket = var.loki_bucket + credentials = "secret:${juju_secret.loki_s3_credentials[0].secret_id}" + }, var.s3_integrator.config) + constraints = var.s3_integrator.constraints + model_uuid = var.model_uuid + name = "${var.loki_coordinator.app_name}-s3-integrator" + storage_directives = var.s3_integrator.storage_directives + trust = true + units = var.s3_integrator.units + + charm { + name = "s3-integrator" + channel = local.channels.s3_integrator + revision = local.revisions.s3_integrator + } +} + +resource "juju_secret" "mimir_s3_credentials" { + count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid + name = "mimir-s3-credentials" + value = { + access-key = var.s3_access_key + secret-key = var.s3_secret_key + } + info = "S3 credentials for Mimir" +} + +resource "juju_access_secret" "mimir_s3_credentials_access" { + count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid + applications = [juju_application.s3_integrator_mimir[0].name] + secret_id = juju_secret.mimir_s3_credentials[0].secret_id +} + +resource "juju_application" "s3_integrator_mimir" { + count = var.storage_backend == "s3" ? 1 : 0 + config = merge({ + endpoint = var.s3_endpoint + bucket = var.mimir_bucket + credentials = "secret:${juju_secret.mimir_s3_credentials[0].secret_id}" + }, var.s3_integrator.config) + constraints = var.s3_integrator.constraints + model_uuid = var.model_uuid + name = "${var.mimir_coordinator.app_name}-s3-integrator" + storage_directives = var.s3_integrator.storage_directives + trust = true + units = var.s3_integrator.units + + charm { + name = "s3-integrator" + channel = local.channels.s3_integrator + revision = local.revisions.s3_integrator + } +} + +resource "juju_secret" "tempo_s3_credentials" { + count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid + name = "tempo-s3-credentials" + value = { + access-key = var.s3_access_key + secret-key = var.s3_secret_key + } + info = "S3 credentials for Tempo" +} + +resource "juju_access_secret" "tempo_s3_credentials_access" { + count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid + applications = [juju_application.s3_integrator_tempo[0].name] + secret_id = juju_secret.tempo_s3_credentials[0].secret_id +} + +resource "juju_application" "s3_integrator_tempo" { + count = var.storage_backend == "s3" ? 1 : 0 + config = merge({ + endpoint = var.s3_endpoint + bucket = var.tempo_bucket + credentials = "secret:${juju_secret.tempo_s3_credentials[0].secret_id}" + }, var.s3_integrator.config) + constraints = var.s3_integrator.constraints + model_uuid = var.model_uuid + name = "${var.tempo_coordinator.app_name}-s3-integrator" + storage_directives = var.s3_integrator.storage_directives + trust = true + units = var.s3_integrator.units + + charm { + name = "s3-integrator" + channel = local.channels.s3_integrator + revision = local.revisions.s3_integrator + } +} diff --git a/terraform/cos-dev/integrations.tf b/terraform/cos-dev/integrations.tf index 966ef8a2..a60e68f2 100644 --- a/terraform/cos-dev/integrations.tf +++ b/terraform/cos-dev/integrations.tf @@ -649,14 +649,14 @@ resource "juju_integration" "ingress" { app_name = module.mimir_coordinator.app_name endpoint = "ingress" } - } : k => v if var.ingress[k] + } : k => v if local.traefik_enabled && var.ingress[k] } model_uuid = var.model_uuid application { - name = module.traefik.app_name - endpoint = module.traefik.endpoints.ingress + name = module.traefik[0].app_name + endpoint = module.traefik[0].endpoints.ingress } application { @@ -666,7 +666,7 @@ resource "juju_integration" "ingress" { } resource "juju_integration" "grafana_ingress" { - count = var.ingress.grafana ? 1 : 0 + count = local.traefik_enabled && var.ingress.grafana ? 1 : 0 model_uuid = var.model_uuid @@ -676,8 +676,8 @@ resource "juju_integration" "grafana_ingress" { } application { - name = module.traefik.app_name - endpoint = module.traefik.endpoints.ingress + name = module.traefik[0].app_name + endpoint = module.traefik[0].endpoints.ingress } lifecycle { replace_triggered_by = [terraform_data.grafana_ingress_interface, terraform_data.grafana_litestream_resource] } @@ -694,14 +694,14 @@ resource "juju_integration" "traefik_route" { app_name = module.tempo_coordinator.app_name endpoint = module.tempo_coordinator.requires.ingress } - } : k => v if var.ingress[k] + } : k => v if local.traefik_enabled && var.ingress[k] } model_uuid = var.model_uuid application { - name = module.traefik.app_name - endpoint = module.traefik.endpoints.traefik_route + name = module.traefik[0].app_name + endpoint = module.traefik[0].endpoints.traefik_route } application { @@ -774,7 +774,7 @@ resource "juju_integration" "internal_certificates" { } resource "juju_integration" "traefik_receive_ca_certificate" { - count = var.internal_tls ? 1 : 0 + count = local.traefik_enabled && var.internal_tls ? 1 : 0 model_uuid = var.model_uuid application { @@ -783,24 +783,21 @@ resource "juju_integration" "traefik_receive_ca_certificate" { } application { - name = module.traefik.app_name - endpoint = module.traefik.endpoints.receive_ca_cert + name = module.traefik[0].app_name + endpoint = module.traefik[0].endpoints.receive_ca_cert } } # -------------- # Provided by an external CA -------------- resource "juju_integration" "external_traefik_certificates" { - count = local.tls_termination ? 1 : 0 + count = local.traefik_enabled && local.tls_termination ? 1 : 0 model_uuid = var.model_uuid + application { offer_url = var.external_certificates_offer_url } application { - offer_url = var.external_certificates_offer_url - } - - application { - name = module.traefik.app_name - endpoint = module.traefik.endpoints.certificates + name = module.traefik[0].app_name + endpoint = module.traefik[0].endpoints.certificates } } @@ -808,10 +805,7 @@ resource "juju_integration" "external_grafana_ca_cert" { count = local.tls_termination ? 1 : 0 model_uuid = var.model_uuid - application { - offer_url = var.external_ca_cert_offer_url - } - + application { offer_url = var.external_ca_cert_offer_url } application { name = module.grafana.app_name endpoint = module.grafana.requires.receive_ca_cert @@ -822,10 +816,7 @@ resource "juju_integration" "external_otelcol_ca_cert" { count = local.tls_termination ? 1 : 0 model_uuid = var.model_uuid - application { - offer_url = var.external_ca_cert_offer_url - } - + application { offer_url = var.external_ca_cert_offer_url } application { name = module.opentelemetry_collector.app_name endpoint = module.opentelemetry_collector.requires.receive_ca_cert @@ -861,4 +852,3 @@ resource "juju_integration" "traces_and_metrics_correlation" { endpoint = "send-datasource" } } - diff --git a/terraform/cos-dev/locals.tf b/terraform/cos-dev/locals.tf index cd9c490b..e4431fed 100644 --- a/terraform/cos-dev/locals.tf +++ b/terraform/cos-dev/locals.tf @@ -1,5 +1,7 @@ locals { tls_termination = var.external_certificates_offer_url != null ? true : false + reverse_proxy_enabled = anytrue(values(var.ingress)) + traefik_enabled = local.reverse_proxy_enabled traefik_base = "ubuntu@20.04" tracks = { alertmanager = "dev" diff --git a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl index bb4f421a..830d624c 100644 --- a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl +++ b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl @@ -1,34 +1,14 @@ mock_provider "juju" {} -variables { - model_uuid = "00000000-0000-0000-0000-000000000000" -} - -# --- Default: all ingress integrations enabled --- +variables { model_uuid = "00000000-0000-0000-0000-000000000000" } -run "default_ingress_all_enabled" { - command = plan +# TODO: This feature also depends on the x2 Traefik story, maybe internal_tls is not the right name +# TODO: Do we need to remove offers / outputs TF conditionally? +# TODO: We need to keep the COS API the same across products: feature in COS, COS Lite, and COS Dev - assert { - condition = length(juju_integration.ingress) == 4 - error_message = "Expected 4 ingress integrations (alertmanager, catalogue, loki, mimir), got ${length(juju_integration.ingress)}" - } +# --- traefik: all ingress disabled --- - # Grafana uses a separate count-based resource due to lifecycle replace_triggered_by - assert { - condition = length(juju_integration.grafana_ingress) == 1 - error_message = "Expected 1 grafana_ingress integration, got ${length(juju_integration.grafana_ingress)}" - } - - assert { - condition = length(juju_integration.traefik_route) == 2 - error_message = "Expected 2 traefik_route integrations (opentelemetry_collector, tempo), got ${length(juju_integration.traefik_route)}" - } -} - -# --- All ingress disabled --- - -run "ingress_all_disabled" { +run "traefik_ingress_disabled" { command = plan variables { @@ -44,121 +24,60 @@ run "ingress_all_disabled" { } assert { - condition = length(juju_integration.ingress) == 0 - error_message = "Expected 0 ingress integrations, got ${length(juju_integration.ingress)}" - } - - assert { - condition = length(juju_integration.grafana_ingress) == 0 - error_message = "Expected 0 grafana_ingress integrations, got ${length(juju_integration.grafana_ingress)}" - } - - assert { - condition = length(juju_integration.traefik_route) == 0 - error_message = "Expected 0 traefik_route integrations, got ${length(juju_integration.traefik_route)}" - } -} - -# --- Only grafana exposed --- - -run "ingress_only_grafana" { - command = plan - - variables { - ingress = { - alertmanager = false - catalogue = false - grafana = true - loki = false - mimir = false - opentelemetry_collector = false - tempo = false - } + condition = length(module.traefik) == 0 + error_message = "Expected no traefik module when ingress is disabled" } assert { condition = length(juju_integration.ingress) == 0 - error_message = "Expected 0 ingress integrations, got ${length(juju_integration.ingress)}" + error_message = "Unexpected ingress integrations when ingress is disabled" } assert { - condition = length(juju_integration.grafana_ingress) == 1 - error_message = "Expected 1 grafana_ingress integration, got ${length(juju_integration.grafana_ingress)}" + condition = length(juju_integration.grafana_ingress) == 0 + error_message = "Unexpected grafana_ingress integrations when ingress is disabled" } assert { condition = length(juju_integration.traefik_route) == 0 - error_message = "Expected 0 traefik_route integrations, got ${length(juju_integration.traefik_route)}" + error_message = "Unexpected traefik_route integrations when ingress is disabled" } } -# --- Only tempo exposed (traefik_route) --- +# --- traefik: some ingress enabled --- -run "ingress_only_tempo" { +run "traefik_ingress_enabled" { command = plan variables { ingress = { alertmanager = false - catalogue = false + catalogue = true grafana = false - loki = false + loki = true mimir = false - opentelemetry_collector = false - tempo = true - } - } - - assert { - condition = length(juju_integration.ingress) == 0 - error_message = "Expected 0 ingress integrations, got ${length(juju_integration.ingress)}" - } - - assert { - condition = length(juju_integration.traefik_route) == 1 - error_message = "Expected 1 traefik_route integration (tempo), got ${length(juju_integration.traefik_route)}" - } - - assert { - condition = contains(keys(juju_integration.traefik_route), "tempo") - error_message = "Expected traefik_route to contain 'tempo' key" - } -} - -# --- Partial override: disable alertmanager and tempo --- - -run "ingress_partial_override" { - command = plan - - variables { - ingress = { - alertmanager = false - tempo = false + opentelemetry_collector = true + tempo = false } } assert { - condition = length(juju_integration.ingress) == 3 - error_message = "Expected 3 ingress integrations (catalogue, loki, mimir), got ${length(juju_integration.ingress)}" + condition = length(module.traefik) == 1 + error_message = "Expected a traefik module when ingress is enabled" } assert { - condition = !contains(keys(juju_integration.ingress), "alertmanager") - error_message = "Expected ingress to NOT contain 'alertmanager' key" + condition = length(juju_integration.ingress) == 2 + error_message = "Unexpected ingress integrations when ingress is enabled" } assert { - condition = length(juju_integration.grafana_ingress) == 1 - error_message = "Expected 1 grafana_ingress integration, got ${length(juju_integration.grafana_ingress)}" + condition = length(juju_integration.grafana_ingress) == 0 + error_message = "Unexpected grafana_ingress integrations when ingress is enabled" } assert { condition = length(juju_integration.traefik_route) == 1 - error_message = "Expected 1 traefik_route integration (opentelemetry_collector), got ${length(juju_integration.traefik_route)}" - } - - assert { - condition = contains(keys(juju_integration.traefik_route), "opentelemetry_collector") - error_message = "Expected traefik_route to contain 'opentelemetry_collector' key" + error_message = "Unexpected traefik_route integrations when ingress is enabled" } } diff --git a/terraform/cos-dev/tests/input_validation.tftest.hcl b/terraform/cos-dev/tests/input_validation.tftest.hcl new file mode 100644 index 00000000..df0d243b --- /dev/null +++ b/terraform/cos-dev/tests/input_validation.tftest.hcl @@ -0,0 +1,47 @@ +mock_provider "juju" {} + +variables { model_uuid = "00000000-0000-0000-0000-000000000000" } + +# --- default: internal_tls enabled --- + +run "internal_tls_enabled" { + command = plan + + assert { + condition = length(module.ssc) == 1 + error_message = "Expected ssc module when internal_tls is enabled" + } + + assert { + condition = length(juju_integration.internal_certificates) == 7 + error_message = "Unexpected internal_certificates integrations when internal_tls is enabled" + } + + assert { + condition = length(juju_integration.traefik_receive_ca_certificate) == 1 + error_message = "Unexpected traefik_receive_ca_certificate integrations when internal_tls is enabled" + } +} + +# --- internal_tls disabled: no ingress via traefik --- + +run "internal_tls_disabled" { + command = plan + + variables { internal_tls = false } + + assert { + condition = length(module.ssc) == 0 + error_message = "Expected no self-signed-certificates module when internal_tls is disabled" + } + + assert { + condition = length(juju_integration.internal_certificates) == 0 + error_message = "Unexpected internal_certificates integrations when internal_tls is disabled" + } + + assert { + condition = length(juju_integration.traefik_receive_ca_certificate) == 0 + error_message = "Unexpected traefik_receive_ca_certificate integrations when internal_tls is disabled" + } +} diff --git a/terraform/cos-dev/tests/internal_tls.tftest.hcl b/terraform/cos-dev/tests/internal_tls.tftest.hcl new file mode 100644 index 00000000..df0d243b --- /dev/null +++ b/terraform/cos-dev/tests/internal_tls.tftest.hcl @@ -0,0 +1,47 @@ +mock_provider "juju" {} + +variables { model_uuid = "00000000-0000-0000-0000-000000000000" } + +# --- default: internal_tls enabled --- + +run "internal_tls_enabled" { + command = plan + + assert { + condition = length(module.ssc) == 1 + error_message = "Expected ssc module when internal_tls is enabled" + } + + assert { + condition = length(juju_integration.internal_certificates) == 7 + error_message = "Unexpected internal_certificates integrations when internal_tls is enabled" + } + + assert { + condition = length(juju_integration.traefik_receive_ca_certificate) == 1 + error_message = "Unexpected traefik_receive_ca_certificate integrations when internal_tls is enabled" + } +} + +# --- internal_tls disabled: no ingress via traefik --- + +run "internal_tls_disabled" { + command = plan + + variables { internal_tls = false } + + assert { + condition = length(module.ssc) == 0 + error_message = "Expected no self-signed-certificates module when internal_tls is disabled" + } + + assert { + condition = length(juju_integration.internal_certificates) == 0 + error_message = "Unexpected internal_certificates integrations when internal_tls is disabled" + } + + assert { + condition = length(juju_integration.traefik_receive_ca_certificate) == 0 + error_message = "Unexpected traefik_receive_ca_certificate integrations when internal_tls is disabled" + } +} diff --git a/terraform/cos-dev/variables.tf b/terraform/cos-dev/variables.tf index d1d871fb..1a266366 100644 --- a/terraform/cos-dev/variables.tf +++ b/terraform/cos-dev/variables.tf @@ -46,7 +46,7 @@ variable "storage_backend" { } } -# -------------- # TLS configurations -------------- +# -------------- # Network configurations -------------- variable "internal_tls" { description = "Specify whether to use TLS or not for internal COS communication. By default, TLS is enabled using self-signed-certificates" @@ -130,18 +130,6 @@ variable "tempo_bucket" { default = "tempo" } -variable "s3_integrator" { - type = object({ - config = optional(map(string), {}) - constraints = optional(string, "arch=amd64") - revision = optional(number, null) - storage_directives = optional(map(string), {}) - units = optional(number, 1) - }) - default = {} - description = "Application configuration shared by all S3-integrators (one deployed per coordinated worker). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application" -} - # -------------- # Application configurations -------------- variable "alertmanager" { @@ -296,6 +284,18 @@ variable "ssc" { description = "Application configuration for Self-signed-certificates. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application" } +variable "s3_integrator" { + type = object({ + config = optional(map(string), {}) + constraints = optional(string, "arch=amd64") + revision = optional(number, null) + storage_directives = optional(map(string), {}) + units = optional(number, 1) + }) + default = {} + description = "Application configuration shared by all S3-integrators (one deployed per coordinated worker). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application" +} + variable "tempo_coordinator" { type = object({ app_name = optional(string, "tempo") From 567dfce80149f7b8f575d779adc4bade71b03c37 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Thu, 21 May 2026 12:36:23 -0400 Subject: [PATCH 11/21] chore --- terraform/cos-dev/locals.tf | 6 +- terraform/cos-dev/outputs.tf | 4 +- .../tests/conditional_ingress.tftest.hcl | 60 +++++++------------ .../cos-dev/tests/revision_pin.tftest.hcl | 4 +- .../tests/topology_and_storage.tftest.hcl | 4 +- 5 files changed, 29 insertions(+), 49 deletions(-) diff --git a/terraform/cos-dev/locals.tf b/terraform/cos-dev/locals.tf index e4431fed..dddec69c 100644 --- a/terraform/cos-dev/locals.tf +++ b/terraform/cos-dev/locals.tf @@ -1,8 +1,8 @@ locals { - tls_termination = var.external_certificates_offer_url != null ? true : false + tls_termination = var.external_certificates_offer_url != null ? true : false reverse_proxy_enabled = anytrue(values(var.ingress)) - traefik_enabled = local.reverse_proxy_enabled - traefik_base = "ubuntu@20.04" + traefik_enabled = local.reverse_proxy_enabled + traefik_base = "ubuntu@20.04" tracks = { alertmanager = "dev" catalogue = "dev" diff --git a/terraform/cos-dev/outputs.tf b/terraform/cos-dev/outputs.tf index a9cfbd75..11f50d6d 100644 --- a/terraform/cos-dev/outputs.tf +++ b/terraform/cos-dev/outputs.tf @@ -30,7 +30,7 @@ output "components" { mimir_worker_write = try(module.mimir_worker_write[0], null) opentelemetry_collector = module.opentelemetry_collector seaweedfs = try(module.seaweedfs[0], null) - ssc = module.ssc + ssc = try(module.ssc[0], null) tempo_coordinator = module.tempo_coordinator tempo_worker = try(module.tempo_worker[0], null) tempo_worker_querier = try(module.tempo_worker_querier[0], null) @@ -39,7 +39,7 @@ output "components" { tempo_worker_distributor = try(module.tempo_worker_distributor[0], null) tempo_worker_compactor = try(module.tempo_worker_compactor[0], null) tempo_worker_metrics_generator = try(module.tempo_worker_metrics_generator[0], null) - traefik = module.traefik + traefik = try(module.traefik[0], null) } description = "All Terraform charm modules which make up this product module" } diff --git a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl index 830d624c..de2972a4 100644 --- a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl +++ b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl @@ -2,82 +2,66 @@ mock_provider "juju" {} variables { model_uuid = "00000000-0000-0000-0000-000000000000" } -# TODO: This feature also depends on the x2 Traefik story, maybe internal_tls is not the right name -# TODO: Do we need to remove offers / outputs TF conditionally? -# TODO: We need to keep the COS API the same across products: feature in COS, COS Lite, and COS Dev +# --- traefik: all ingress enabled by default --- -# --- traefik: all ingress disabled --- - -run "traefik_ingress_disabled" { +run "traefik_ingress_enabled" { command = plan - variables { - ingress = { - alertmanager = false - catalogue = false - grafana = false - loki = false - mimir = false - opentelemetry_collector = false - tempo = false - } - } - assert { - condition = length(module.traefik) == 0 - error_message = "Expected no traefik module when ingress is disabled" + condition = length(module.traefik) == 1 + error_message = "Expected a traefik module when ingress is enabled" } assert { - condition = length(juju_integration.ingress) == 0 - error_message = "Unexpected ingress integrations when ingress is disabled" + condition = length(juju_integration.ingress) == 4 + error_message = "Unexpected ingress integrations when ingress is enabled" } assert { - condition = length(juju_integration.grafana_ingress) == 0 - error_message = "Unexpected grafana_ingress integrations when ingress is disabled" + condition = length(juju_integration.grafana_ingress) == 1 + error_message = "Unexpected grafana_ingress integrations when ingress is enabled" } assert { - condition = length(juju_integration.traefik_route) == 0 - error_message = "Unexpected traefik_route integrations when ingress is disabled" + condition = length(juju_integration.traefik_route) == 2 + error_message = "Unexpected traefik_route integrations when ingress is enabled" } } -# --- traefik: some ingress enabled --- +# --- traefik: all ingress disabled --- -run "traefik_ingress_enabled" { +run "traefik_ingress_disabled" { command = plan variables { ingress = { alertmanager = false - catalogue = true + catalogue = false grafana = false - loki = true + loki = false mimir = false - opentelemetry_collector = true + opentelemetry_collector = false tempo = false } } assert { - condition = length(module.traefik) == 1 - error_message = "Expected a traefik module when ingress is enabled" + condition = length(module.traefik) == 0 + error_message = "Expected no traefik module when ingress is disabled" } assert { - condition = length(juju_integration.ingress) == 2 - error_message = "Unexpected ingress integrations when ingress is enabled" + condition = length(juju_integration.ingress) == 0 + error_message = "Unexpected ingress integrations when ingress is disabled" } assert { condition = length(juju_integration.grafana_ingress) == 0 - error_message = "Unexpected grafana_ingress integrations when ingress is enabled" + error_message = "Unexpected grafana_ingress integrations when ingress is disabled" } assert { - condition = length(juju_integration.traefik_route) == 1 - error_message = "Unexpected traefik_route integrations when ingress is enabled" + condition = length(juju_integration.traefik_route) == 0 + error_message = "Unexpected traefik_route integrations when ingress is disabled" } } diff --git a/terraform/cos-dev/tests/revision_pin.tftest.hcl b/terraform/cos-dev/tests/revision_pin.tftest.hcl index 7cf5f32e..6e68c9ac 100644 --- a/terraform/cos-dev/tests/revision_pin.tftest.hcl +++ b/terraform/cos-dev/tests/revision_pin.tftest.hcl @@ -1,8 +1,6 @@ mock_provider "juju" {} -variables { - model_uuid = "00000000-0000-0000-0000-000000000000" -} +variables { model_uuid = "00000000-0000-0000-0000-000000000000" } # --- User revision pin is respected and not overridden by juju_charm datasource --- diff --git a/terraform/cos-dev/tests/topology_and_storage.tftest.hcl b/terraform/cos-dev/tests/topology_and_storage.tftest.hcl index ffb3b5c0..7caee954 100644 --- a/terraform/cos-dev/tests/topology_and_storage.tftest.hcl +++ b/terraform/cos-dev/tests/topology_and_storage.tftest.hcl @@ -1,8 +1,6 @@ mock_provider "juju" {} -variables { - model_uuid = "00000000-0000-0000-0000-000000000000" -} +variables { model_uuid = "00000000-0000-0000-0000-000000000000" } # --- Default: monolithic topology with seaweedfs --- From f5120abcdf9fb0e696dc774a1efee9041427d97e Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Thu, 21 May 2026 13:19:03 -0400 Subject: [PATCH 12/21] chore --- .../cos-dev/tests/input_validation.tftest.hcl | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/terraform/cos-dev/tests/input_validation.tftest.hcl b/terraform/cos-dev/tests/input_validation.tftest.hcl index df0d243b..e7833149 100644 --- a/terraform/cos-dev/tests/input_validation.tftest.hcl +++ b/terraform/cos-dev/tests/input_validation.tftest.hcl @@ -2,46 +2,50 @@ mock_provider "juju" {} variables { model_uuid = "00000000-0000-0000-0000-000000000000" } -# --- default: internal_tls enabled --- +# --- external cert URLs both null: no validation error --- -run "internal_tls_enabled" { +run "external_cert_urls_both_null" { command = plan - assert { - condition = length(module.ssc) == 1 - error_message = "Expected ssc module when internal_tls is enabled" + variables { + external_certificates_offer_url = null + external_ca_cert_offer_url = null } +} - assert { - condition = length(juju_integration.internal_certificates) == 7 - error_message = "Unexpected internal_certificates integrations when internal_tls is enabled" - } +# --- external cert URLs both set: no validation error --- + +run "external_cert_urls_both_set" { + command = plan - assert { - condition = length(juju_integration.traefik_receive_ca_certificate) == 1 - error_message = "Unexpected traefik_receive_ca_certificate integrations when internal_tls is enabled" + variables { + external_certificates_offer_url = "admin/external-ca.tls-certificates" + external_ca_cert_offer_url = "admin/external-ca.send-ca-cert" } } -# --- internal_tls disabled: no ingress via traefik --- +# --- only external_certificates_offer_url set: validation error --- -run "internal_tls_disabled" { +run "external_certificates_offer_url_only_fails" { command = plan - variables { internal_tls = false } - - assert { - condition = length(module.ssc) == 0 - error_message = "Expected no self-signed-certificates module when internal_tls is disabled" + variables { + external_certificates_offer_url = "admin/external-ca.tls-certificates" + external_ca_cert_offer_url = null } - assert { - condition = length(juju_integration.internal_certificates) == 0 - error_message = "Unexpected internal_certificates integrations when internal_tls is disabled" - } + expect_failures = [var.external_certificates_offer_url] +} + +# --- only external_ca_cert_offer_url set: validation error --- - assert { - condition = length(juju_integration.traefik_receive_ca_certificate) == 0 - error_message = "Unexpected traefik_receive_ca_certificate integrations when internal_tls is disabled" +run "external_ca_cert_offer_url_only_fails" { + command = plan + + variables { + external_certificates_offer_url = null + external_ca_cert_offer_url = "admin/external-ca.send-ca-cert" } + + expect_failures = [var.external_certificates_offer_url] } From 5e1c5c48f6bbbe609f66fe15f1da02830cebb7c2 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Fri, 22 May 2026 13:36:52 -0400 Subject: [PATCH 13/21] chore --- .../{input_validation.tftest.hcl => external_tls.tftest.hcl} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename terraform/cos-dev/tests/{input_validation.tftest.hcl => external_tls.tftest.hcl} (100%) diff --git a/terraform/cos-dev/tests/input_validation.tftest.hcl b/terraform/cos-dev/tests/external_tls.tftest.hcl similarity index 100% rename from terraform/cos-dev/tests/input_validation.tftest.hcl rename to terraform/cos-dev/tests/external_tls.tftest.hcl From 2be777525a8dab9120b8df9d9514b1d6691a8236 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Fri, 22 May 2026 13:45:20 -0400 Subject: [PATCH 14/21] chore --- terraform/cos-dev/applications.tf | 63 +++++++++++++++++++------------ terraform/cos-dev/integrations.tf | 33 ++++++++++++++-- 2 files changed, 68 insertions(+), 28 deletions(-) diff --git a/terraform/cos-dev/applications.tf b/terraform/cos-dev/applications.tf index e0857be3..a7b030c7 100644 --- a/terraform/cos-dev/applications.tf +++ b/terraform/cos-dev/applications.tf @@ -49,8 +49,8 @@ module "loki_coordinator" { # Monolithic: single all-in-one worker module "loki_worker" { - count = var.topology == "monolithic" ? 1 : 0 source = "git::https://github.com/canonical/loki-operators//worker/terraform" + count = var.topology == "monolithic" ? 1 : 0 depends_on = [module.loki_coordinator] app_name = var.loki_worker.app_name @@ -65,8 +65,8 @@ module "loki_worker" { # Distributed: separate backend, read, and write workers module "loki_worker_backend" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/loki-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.loki_coordinator] app_name = "${var.loki_worker.app_name}-backend" @@ -80,8 +80,8 @@ module "loki_worker_backend" { } module "loki_worker_read" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/loki-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.loki_coordinator] app_name = "${var.loki_worker.app_name}-read" @@ -95,8 +95,8 @@ module "loki_worker_read" { } module "loki_worker_write" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/loki-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.loki_coordinator] app_name = "${var.loki_worker.app_name}-write" @@ -110,7 +110,8 @@ module "loki_worker_write" { } module "mimir_coordinator" { - source = "git::https://github.com/canonical/mimir-operators//coordinator/terraform" + source = "git::https://github.com/canonical/mimir-operators//coordinator/terraform" + app_name = var.mimir_coordinator.app_name channel = local.channels.mimir config = var.mimir_coordinator.config @@ -123,8 +124,8 @@ module "mimir_coordinator" { # Monolithic: single all-in-one worker module "mimir_worker" { - count = var.topology == "monolithic" ? 1 : 0 source = "git::https://github.com/canonical/mimir-operators//worker/terraform" + count = var.topology == "monolithic" ? 1 : 0 depends_on = [module.mimir_coordinator] app_name = var.mimir_worker.app_name @@ -139,8 +140,8 @@ module "mimir_worker" { # Distributed: separate backend, read, and write workers module "mimir_worker_backend" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/mimir-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.mimir_coordinator] app_name = "${var.mimir_worker.app_name}-backend" @@ -154,8 +155,8 @@ module "mimir_worker_backend" { } module "mimir_worker_read" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/mimir-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.mimir_coordinator] app_name = "${var.mimir_worker.app_name}-read" @@ -169,8 +170,8 @@ module "mimir_worker_read" { } module "mimir_worker_write" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/mimir-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.mimir_coordinator] app_name = "${var.mimir_worker.app_name}-write" @@ -184,7 +185,8 @@ module "mimir_worker_write" { } module "opentelemetry_collector" { - source = "git::https://github.com/canonical/opentelemetry-collector-k8s-operator//terraform" + source = "git::https://github.com/canonical/opentelemetry-collector-k8s-operator//terraform" + app_name = var.opentelemetry_collector.app_name channel = local.channels.otelcol config = var.opentelemetry_collector.config @@ -196,8 +198,9 @@ module "opentelemetry_collector" { } module "seaweedfs" { - count = var.storage_backend == "seaweedfs" ? 1 : 0 - source = "git::https://github.com/canonical/observability-stack//terraform/seaweedfs" + source = "git::https://github.com/canonical/observability-stack//terraform/seaweedfs" + count = var.storage_backend == "seaweedfs" ? 1 : 0 + app_name = var.seaweedfs.app_name channel = local.channels.seaweedfs config = var.seaweedfs.config @@ -209,8 +212,9 @@ module "seaweedfs" { } module "ssc" { - count = var.internal_tls ? 1 : 0 - source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform" + source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform" + count = var.internal_tls ? 1 : 0 + app_name = var.ssc.app_name channel = local.channels.ssc config = var.ssc.config @@ -221,7 +225,8 @@ module "ssc" { } module "tempo_coordinator" { - source = "git::https://github.com/canonical/tempo-operators//coordinator/terraform" + source = "git::https://github.com/canonical/tempo-operators//coordinator/terraform" + app_name = var.tempo_coordinator.app_name channel = local.channels.tempo config = var.tempo_coordinator.config @@ -234,8 +239,8 @@ module "tempo_coordinator" { # Monolithic: single all-in-one worker module "tempo_worker" { - count = var.topology == "monolithic" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "monolithic" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = var.tempo_worker.app_name @@ -250,8 +255,8 @@ module "tempo_worker" { # Distributed: separate workers per role module "tempo_worker_querier" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = "${var.tempo_worker.app_name}-querier" @@ -265,8 +270,8 @@ module "tempo_worker_querier" { } module "tempo_worker_query_frontend" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = "${var.tempo_worker.app_name}-query-frontend" @@ -280,8 +285,8 @@ module "tempo_worker_query_frontend" { } module "tempo_worker_ingester" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = "${var.tempo_worker.app_name}-ingester" @@ -295,8 +300,8 @@ module "tempo_worker_ingester" { } module "tempo_worker_distributor" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = "${var.tempo_worker.app_name}-distributor" @@ -310,8 +315,8 @@ module "tempo_worker_distributor" { } module "tempo_worker_compactor" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = "${var.tempo_worker.app_name}-compactor" @@ -325,8 +330,8 @@ module "tempo_worker_compactor" { } module "tempo_worker_metrics_generator" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = "${var.tempo_worker.app_name}-metrics-generator" @@ -340,8 +345,9 @@ module "tempo_worker_metrics_generator" { } module "traefik" { - count = local.traefik_enabled ? 1 : 0 - source = "git::https://github.com/canonical/traefik-k8s-operator//terraform" + source = "git::https://github.com/canonical/traefik-k8s-operator//terraform" + count = local.traefik_enabled ? 1 : 0 + app_name = var.traefik.app_name channel = local.channels.traefik config = var.traefik.config @@ -356,6 +362,7 @@ module "traefik" { resource "juju_secret" "loki_s3_credentials" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid name = "loki-s3-credentials" value = { @@ -367,6 +374,7 @@ resource "juju_secret" "loki_s3_credentials" { resource "juju_access_secret" "loki_s3_credentials_access" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid applications = [juju_application.s3_integrator_loki[0].name] secret_id = juju_secret.loki_s3_credentials[0].secret_id @@ -375,6 +383,7 @@ resource "juju_access_secret" "loki_s3_credentials_access" { # TODO: Replace with a remote terraform module once the s3-integrator charm exposes one. resource "juju_application" "s3_integrator_loki" { count = var.storage_backend == "s3" ? 1 : 0 + config = merge({ endpoint = var.s3_endpoint bucket = var.loki_bucket @@ -396,6 +405,7 @@ resource "juju_application" "s3_integrator_loki" { resource "juju_secret" "mimir_s3_credentials" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid name = "mimir-s3-credentials" value = { @@ -407,6 +417,7 @@ resource "juju_secret" "mimir_s3_credentials" { resource "juju_access_secret" "mimir_s3_credentials_access" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid applications = [juju_application.s3_integrator_mimir[0].name] secret_id = juju_secret.mimir_s3_credentials[0].secret_id @@ -414,6 +425,7 @@ resource "juju_access_secret" "mimir_s3_credentials_access" { resource "juju_application" "s3_integrator_mimir" { count = var.storage_backend == "s3" ? 1 : 0 + config = merge({ endpoint = var.s3_endpoint bucket = var.mimir_bucket @@ -435,6 +447,7 @@ resource "juju_application" "s3_integrator_mimir" { resource "juju_secret" "tempo_s3_credentials" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid name = "tempo-s3-credentials" value = { @@ -446,6 +459,7 @@ resource "juju_secret" "tempo_s3_credentials" { resource "juju_access_secret" "tempo_s3_credentials_access" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid applications = [juju_application.s3_integrator_tempo[0].name] secret_id = juju_secret.tempo_s3_credentials[0].secret_id @@ -453,6 +467,7 @@ resource "juju_access_secret" "tempo_s3_credentials_access" { resource "juju_application" "s3_integrator_tempo" { count = var.storage_backend == "s3" ? 1 : 0 + config = merge({ endpoint = var.s3_endpoint bucket = var.tempo_bucket diff --git a/terraform/cos-dev/integrations.tf b/terraform/cos-dev/integrations.tf index a60e68f2..4377d5ef 100644 --- a/terraform/cos-dev/integrations.tf +++ b/terraform/cos-dev/integrations.tf @@ -3,6 +3,7 @@ # Monolithic: coordinator ↔ single all-in-one worker resource "juju_integration" "loki_cluster" { count = var.topology == "monolithic" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -19,6 +20,7 @@ resource "juju_integration" "loki_cluster" { # Distributed: coordinator ↔ separate backend, read, write workers resource "juju_integration" "loki_cluster_backend" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -34,6 +36,7 @@ resource "juju_integration" "loki_cluster_backend" { resource "juju_integration" "loki_cluster_read" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -49,6 +52,7 @@ resource "juju_integration" "loki_cluster_read" { resource "juju_integration" "loki_cluster_write" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -64,6 +68,7 @@ resource "juju_integration" "loki_cluster_write" { resource "juju_integration" "mimir_cluster" { count = var.topology == "monolithic" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -79,6 +84,7 @@ resource "juju_integration" "mimir_cluster" { resource "juju_integration" "mimir_cluster_backend" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -94,6 +100,7 @@ resource "juju_integration" "mimir_cluster_backend" { resource "juju_integration" "mimir_cluster_read" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -109,6 +116,7 @@ resource "juju_integration" "mimir_cluster_read" { resource "juju_integration" "mimir_cluster_write" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -124,6 +132,7 @@ resource "juju_integration" "mimir_cluster_write" { resource "juju_integration" "tempo_cluster" { count = var.topology == "monolithic" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -139,6 +148,7 @@ resource "juju_integration" "tempo_cluster" { resource "juju_integration" "tempo_cluster_querier" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -154,6 +164,7 @@ resource "juju_integration" "tempo_cluster_querier" { resource "juju_integration" "tempo_cluster_query_frontend" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -169,6 +180,7 @@ resource "juju_integration" "tempo_cluster_query_frontend" { resource "juju_integration" "tempo_cluster_ingester" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -184,6 +196,7 @@ resource "juju_integration" "tempo_cluster_ingester" { resource "juju_integration" "tempo_cluster_distributor" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -199,6 +212,7 @@ resource "juju_integration" "tempo_cluster_distributor" { resource "juju_integration" "tempo_cluster_compactor" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -214,6 +228,7 @@ resource "juju_integration" "tempo_cluster_compactor" { resource "juju_integration" "tempo_cluster_metrics_generator" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -231,6 +246,7 @@ resource "juju_integration" "tempo_cluster_metrics_generator" { resource "juju_integration" "seaweedfs_loki" { count = var.storage_backend == "seaweedfs" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -246,6 +262,7 @@ resource "juju_integration" "seaweedfs_loki" { resource "juju_integration" "seaweedfs_mimir" { count = var.storage_backend == "seaweedfs" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -261,6 +278,7 @@ resource "juju_integration" "seaweedfs_mimir" { resource "juju_integration" "seaweedfs_tempo" { count = var.storage_backend == "seaweedfs" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -278,6 +296,7 @@ resource "juju_integration" "seaweedfs_tempo" { resource "juju_integration" "s3_integrator_loki" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -293,6 +312,7 @@ resource "juju_integration" "s3_integrator_loki" { resource "juju_integration" "s3_integrator_mimir" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -308,6 +328,7 @@ resource "juju_integration" "s3_integrator_mimir" { resource "juju_integration" "s3_integrator_tempo" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -774,7 +795,8 @@ resource "juju_integration" "internal_certificates" { } resource "juju_integration" "traefik_receive_ca_certificate" { - count = local.traefik_enabled && var.internal_tls ? 1 : 0 + count = local.traefik_enabled && var.internal_tls ? 1 : 0 + model_uuid = var.model_uuid application { @@ -791,7 +813,8 @@ resource "juju_integration" "traefik_receive_ca_certificate" { # -------------- # Provided by an external CA -------------- resource "juju_integration" "external_traefik_certificates" { - count = local.traefik_enabled && local.tls_termination ? 1 : 0 + count = local.traefik_enabled && local.tls_termination ? 1 : 0 + model_uuid = var.model_uuid application { offer_url = var.external_certificates_offer_url } @@ -802,7 +825,8 @@ resource "juju_integration" "external_traefik_certificates" { } resource "juju_integration" "external_grafana_ca_cert" { - count = local.tls_termination ? 1 : 0 + count = local.tls_termination ? 1 : 0 + model_uuid = var.model_uuid application { offer_url = var.external_ca_cert_offer_url } @@ -813,7 +837,8 @@ resource "juju_integration" "external_grafana_ca_cert" { } resource "juju_integration" "external_otelcol_ca_cert" { - count = local.tls_termination ? 1 : 0 + count = local.tls_termination ? 1 : 0 + model_uuid = var.model_uuid application { offer_url = var.external_ca_cert_offer_url } From f91048b023009acc599dba755299099be044bbc2 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Fri, 22 May 2026 13:51:21 -0400 Subject: [PATCH 15/21] chore --- terraform/cos-dev/applications.tf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/terraform/cos-dev/applications.tf b/terraform/cos-dev/applications.tf index a7b030c7..be2d9b58 100644 --- a/terraform/cos-dev/applications.tf +++ b/terraform/cos-dev/applications.tf @@ -1,5 +1,6 @@ module "alertmanager" { source = "git::https://github.com/canonical/alertmanager-k8s-operator//terraform" + app_name = var.alertmanager.app_name channel = local.channels.alertmanager config = var.alertmanager.config @@ -12,6 +13,7 @@ module "alertmanager" { module "catalogue" { source = "git::https://github.com/canonical/catalogue-k8s-operator//terraform" + app_name = var.catalogue.app_name channel = local.channels.catalogue config = var.catalogue.config @@ -24,6 +26,7 @@ module "catalogue" { module "grafana" { source = "git::https://github.com/canonical/grafana-k8s-operator//terraform" + app_name = var.grafana.app_name channel = local.channels.grafana config = var.grafana.config @@ -37,6 +40,7 @@ module "grafana" { module "loki_coordinator" { source = "git::https://github.com/canonical/loki-operators//coordinator/terraform" + app_name = var.loki_coordinator.app_name channel = local.channels.loki config = var.loki_coordinator.config From 34b7a4fa27f35ae7e08b54d631040c4a966a2deb Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Fri, 22 May 2026 13:36:52 -0400 Subject: [PATCH 16/21] chore --- .../{input_validation.tftest.hcl => external_tls.tftest.hcl} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename terraform/cos-dev/tests/{input_validation.tftest.hcl => external_tls.tftest.hcl} (100%) diff --git a/terraform/cos-dev/tests/input_validation.tftest.hcl b/terraform/cos-dev/tests/external_tls.tftest.hcl similarity index 100% rename from terraform/cos-dev/tests/input_validation.tftest.hcl rename to terraform/cos-dev/tests/external_tls.tftest.hcl From 04254ba77a0f84ed409c0a1dfc2ec2be2ad0f93c Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Fri, 22 May 2026 13:45:20 -0400 Subject: [PATCH 17/21] chore --- terraform/cos-dev/applications.tf | 63 +++++++++++++++++++------------ terraform/cos-dev/integrations.tf | 33 ++++++++++++++-- 2 files changed, 68 insertions(+), 28 deletions(-) diff --git a/terraform/cos-dev/applications.tf b/terraform/cos-dev/applications.tf index 0f011f2e..6c8bc8b2 100644 --- a/terraform/cos-dev/applications.tf +++ b/terraform/cos-dev/applications.tf @@ -75,8 +75,8 @@ module "loki_coordinator" { # Monolithic: single all-in-one worker module "loki_worker" { - count = var.topology == "monolithic" ? 1 : 0 source = "git::https://github.com/canonical/loki-operators//worker/terraform" + count = var.topology == "monolithic" ? 1 : 0 depends_on = [module.loki_coordinator] app_name = var.loki_worker.app_name @@ -91,8 +91,8 @@ module "loki_worker" { # Distributed: separate backend, read, and write workers module "loki_worker_backend" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/loki-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.loki_coordinator] app_name = "${var.loki_worker.app_name}-backend" @@ -106,8 +106,8 @@ module "loki_worker_backend" { } module "loki_worker_read" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/loki-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.loki_coordinator] app_name = "${var.loki_worker.app_name}-read" @@ -121,8 +121,8 @@ module "loki_worker_read" { } module "loki_worker_write" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/loki-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.loki_coordinator] app_name = "${var.loki_worker.app_name}-write" @@ -136,7 +136,8 @@ module "loki_worker_write" { } module "mimir_coordinator" { - source = "git::https://github.com/canonical/mimir-operators//coordinator/terraform" + source = "git::https://github.com/canonical/mimir-operators//coordinator/terraform" + app_name = var.mimir_coordinator.app_name channel = local.channels.mimir config = var.mimir_coordinator.config @@ -149,8 +150,8 @@ module "mimir_coordinator" { # Monolithic: single all-in-one worker module "mimir_worker" { - count = var.topology == "monolithic" ? 1 : 0 source = "git::https://github.com/canonical/mimir-operators//worker/terraform" + count = var.topology == "monolithic" ? 1 : 0 depends_on = [module.mimir_coordinator] app_name = var.mimir_worker.app_name @@ -165,8 +166,8 @@ module "mimir_worker" { # Distributed: separate backend, read, and write workers module "mimir_worker_backend" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/mimir-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.mimir_coordinator] app_name = "${var.mimir_worker.app_name}-backend" @@ -180,8 +181,8 @@ module "mimir_worker_backend" { } module "mimir_worker_read" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/mimir-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.mimir_coordinator] app_name = "${var.mimir_worker.app_name}-read" @@ -195,8 +196,8 @@ module "mimir_worker_read" { } module "mimir_worker_write" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/mimir-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.mimir_coordinator] app_name = "${var.mimir_worker.app_name}-write" @@ -210,7 +211,8 @@ module "mimir_worker_write" { } module "opentelemetry_collector" { - source = "git::https://github.com/canonical/opentelemetry-collector-k8s-operator//terraform" + source = "git::https://github.com/canonical/opentelemetry-collector-k8s-operator//terraform" + app_name = var.opentelemetry_collector.app_name channel = local.channels.otelcol config = var.opentelemetry_collector.config @@ -222,8 +224,9 @@ module "opentelemetry_collector" { } module "seaweedfs" { - count = var.storage_backend == "seaweedfs" ? 1 : 0 - source = "git::https://github.com/canonical/observability-stack//terraform/seaweedfs" + source = "git::https://github.com/canonical/observability-stack//terraform/seaweedfs" + count = var.storage_backend == "seaweedfs" ? 1 : 0 + app_name = var.seaweedfs.app_name channel = local.channels.seaweedfs config = var.seaweedfs.config @@ -235,8 +238,9 @@ module "seaweedfs" { } module "ssc" { - count = var.internal_tls ? 1 : 0 - source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform" + source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform" + count = var.internal_tls ? 1 : 0 + app_name = var.ssc.app_name channel = local.channels.ssc config = var.ssc.config @@ -247,7 +251,8 @@ module "ssc" { } module "tempo_coordinator" { - source = "git::https://github.com/canonical/tempo-operators//coordinator/terraform" + source = "git::https://github.com/canonical/tempo-operators//coordinator/terraform" + app_name = var.tempo_coordinator.app_name channel = local.channels.tempo config = var.tempo_coordinator.config @@ -260,8 +265,8 @@ module "tempo_coordinator" { # Monolithic: single all-in-one worker module "tempo_worker" { - count = var.topology == "monolithic" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "monolithic" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = var.tempo_worker.app_name @@ -276,8 +281,8 @@ module "tempo_worker" { # Distributed: separate workers per role module "tempo_worker_querier" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = "${var.tempo_worker.app_name}-querier" @@ -291,8 +296,8 @@ module "tempo_worker_querier" { } module "tempo_worker_query_frontend" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = "${var.tempo_worker.app_name}-query-frontend" @@ -306,8 +311,8 @@ module "tempo_worker_query_frontend" { } module "tempo_worker_ingester" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = "${var.tempo_worker.app_name}-ingester" @@ -321,8 +326,8 @@ module "tempo_worker_ingester" { } module "tempo_worker_distributor" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = "${var.tempo_worker.app_name}-distributor" @@ -336,8 +341,8 @@ module "tempo_worker_distributor" { } module "tempo_worker_compactor" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = "${var.tempo_worker.app_name}-compactor" @@ -351,8 +356,8 @@ module "tempo_worker_compactor" { } module "tempo_worker_metrics_generator" { - count = var.topology == "distributed" ? 1 : 0 source = "git::https://github.com/canonical/tempo-operators//worker/terraform" + count = var.topology == "distributed" ? 1 : 0 depends_on = [module.tempo_coordinator] app_name = "${var.tempo_worker.app_name}-metrics-generator" @@ -366,8 +371,9 @@ module "tempo_worker_metrics_generator" { } module "traefik" { - count = local.traefik_enabled ? 1 : 0 - source = "git::https://github.com/canonical/traefik-k8s-operator//terraform" + source = "git::https://github.com/canonical/traefik-k8s-operator//terraform" + count = local.traefik_enabled ? 1 : 0 + app_name = var.traefik.app_name channel = local.channels.traefik config = var.traefik.config @@ -382,6 +388,7 @@ module "traefik" { resource "juju_secret" "loki_s3_credentials" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid name = "loki-s3-credentials" value = { @@ -393,6 +400,7 @@ resource "juju_secret" "loki_s3_credentials" { resource "juju_access_secret" "loki_s3_credentials_access" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid applications = [juju_application.s3_integrator_loki[0].name] secret_id = juju_secret.loki_s3_credentials[0].secret_id @@ -401,6 +409,7 @@ resource "juju_access_secret" "loki_s3_credentials_access" { # TODO: Replace with a remote terraform module once the s3-integrator charm exposes one. resource "juju_application" "s3_integrator_loki" { count = var.storage_backend == "s3" ? 1 : 0 + config = merge({ endpoint = var.s3_endpoint bucket = var.loki_bucket @@ -422,6 +431,7 @@ resource "juju_application" "s3_integrator_loki" { resource "juju_secret" "mimir_s3_credentials" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid name = "mimir-s3-credentials" value = { @@ -433,6 +443,7 @@ resource "juju_secret" "mimir_s3_credentials" { resource "juju_access_secret" "mimir_s3_credentials_access" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid applications = [juju_application.s3_integrator_mimir[0].name] secret_id = juju_secret.mimir_s3_credentials[0].secret_id @@ -440,6 +451,7 @@ resource "juju_access_secret" "mimir_s3_credentials_access" { resource "juju_application" "s3_integrator_mimir" { count = var.storage_backend == "s3" ? 1 : 0 + config = merge({ endpoint = var.s3_endpoint bucket = var.mimir_bucket @@ -461,6 +473,7 @@ resource "juju_application" "s3_integrator_mimir" { resource "juju_secret" "tempo_s3_credentials" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid name = "tempo-s3-credentials" value = { @@ -472,6 +485,7 @@ resource "juju_secret" "tempo_s3_credentials" { resource "juju_access_secret" "tempo_s3_credentials_access" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid applications = [juju_application.s3_integrator_tempo[0].name] secret_id = juju_secret.tempo_s3_credentials[0].secret_id @@ -479,6 +493,7 @@ resource "juju_access_secret" "tempo_s3_credentials_access" { resource "juju_application" "s3_integrator_tempo" { count = var.storage_backend == "s3" ? 1 : 0 + config = merge({ endpoint = var.s3_endpoint bucket = var.tempo_bucket diff --git a/terraform/cos-dev/integrations.tf b/terraform/cos-dev/integrations.tf index 7aab1d4a..cd604ebe 100644 --- a/terraform/cos-dev/integrations.tf +++ b/terraform/cos-dev/integrations.tf @@ -3,6 +3,7 @@ # Monolithic: coordinator ↔ single all-in-one worker resource "juju_integration" "loki_cluster" { count = var.topology == "monolithic" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -19,6 +20,7 @@ resource "juju_integration" "loki_cluster" { # Distributed: coordinator ↔ separate backend, read, write workers resource "juju_integration" "loki_cluster_backend" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -34,6 +36,7 @@ resource "juju_integration" "loki_cluster_backend" { resource "juju_integration" "loki_cluster_read" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -49,6 +52,7 @@ resource "juju_integration" "loki_cluster_read" { resource "juju_integration" "loki_cluster_write" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -64,6 +68,7 @@ resource "juju_integration" "loki_cluster_write" { resource "juju_integration" "mimir_cluster" { count = var.topology == "monolithic" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -79,6 +84,7 @@ resource "juju_integration" "mimir_cluster" { resource "juju_integration" "mimir_cluster_backend" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -94,6 +100,7 @@ resource "juju_integration" "mimir_cluster_backend" { resource "juju_integration" "mimir_cluster_read" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -109,6 +116,7 @@ resource "juju_integration" "mimir_cluster_read" { resource "juju_integration" "mimir_cluster_write" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -124,6 +132,7 @@ resource "juju_integration" "mimir_cluster_write" { resource "juju_integration" "tempo_cluster" { count = var.topology == "monolithic" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -139,6 +148,7 @@ resource "juju_integration" "tempo_cluster" { resource "juju_integration" "tempo_cluster_querier" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -154,6 +164,7 @@ resource "juju_integration" "tempo_cluster_querier" { resource "juju_integration" "tempo_cluster_query_frontend" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -169,6 +180,7 @@ resource "juju_integration" "tempo_cluster_query_frontend" { resource "juju_integration" "tempo_cluster_ingester" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -184,6 +196,7 @@ resource "juju_integration" "tempo_cluster_ingester" { resource "juju_integration" "tempo_cluster_distributor" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -199,6 +212,7 @@ resource "juju_integration" "tempo_cluster_distributor" { resource "juju_integration" "tempo_cluster_compactor" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -214,6 +228,7 @@ resource "juju_integration" "tempo_cluster_compactor" { resource "juju_integration" "tempo_cluster_metrics_generator" { count = var.topology == "distributed" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -231,6 +246,7 @@ resource "juju_integration" "tempo_cluster_metrics_generator" { resource "juju_integration" "seaweedfs_loki" { count = var.storage_backend == "seaweedfs" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -246,6 +262,7 @@ resource "juju_integration" "seaweedfs_loki" { resource "juju_integration" "seaweedfs_mimir" { count = var.storage_backend == "seaweedfs" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -261,6 +278,7 @@ resource "juju_integration" "seaweedfs_mimir" { resource "juju_integration" "seaweedfs_tempo" { count = var.storage_backend == "seaweedfs" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -278,6 +296,7 @@ resource "juju_integration" "seaweedfs_tempo" { resource "juju_integration" "s3_integrator_loki" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -293,6 +312,7 @@ resource "juju_integration" "s3_integrator_loki" { resource "juju_integration" "s3_integrator_mimir" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -308,6 +328,7 @@ resource "juju_integration" "s3_integrator_mimir" { resource "juju_integration" "s3_integrator_tempo" { count = var.storage_backend == "s3" ? 1 : 0 + model_uuid = var.model_uuid application { @@ -854,7 +875,8 @@ resource "juju_integration" "internal_certificates" { } resource "juju_integration" "traefik_receive_ca_certificate" { - count = local.traefik_enabled && var.internal_tls ? 1 : 0 + count = local.traefik_enabled && var.internal_tls ? 1 : 0 + model_uuid = var.model_uuid application { @@ -871,7 +893,8 @@ resource "juju_integration" "traefik_receive_ca_certificate" { # -------------- # Provided by an external CA -------------- resource "juju_integration" "external_traefik_certificates" { - count = local.traefik_enabled && local.tls_termination ? 1 : 0 + count = local.traefik_enabled && local.tls_termination ? 1 : 0 + model_uuid = var.model_uuid application { offer_url = var.external_certificates_offer_url } @@ -882,7 +905,8 @@ resource "juju_integration" "external_traefik_certificates" { } resource "juju_integration" "external_grafana_ca_cert" { - count = local.tls_termination ? 1 : 0 + count = local.tls_termination ? 1 : 0 + model_uuid = var.model_uuid application { offer_url = var.external_ca_cert_offer_url } @@ -893,7 +917,8 @@ resource "juju_integration" "external_grafana_ca_cert" { } resource "juju_integration" "external_otelcol_ca_cert" { - count = local.tls_termination ? 1 : 0 + count = local.tls_termination ? 1 : 0 + model_uuid = var.model_uuid application { offer_url = var.external_ca_cert_offer_url } From 1b046e298fd5601e63d75617a8582ba2dd52e46f Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Fri, 22 May 2026 13:51:21 -0400 Subject: [PATCH 18/21] chore --- terraform/cos-dev/applications.tf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/terraform/cos-dev/applications.tf b/terraform/cos-dev/applications.tf index 6c8bc8b2..cdf32467 100644 --- a/terraform/cos-dev/applications.tf +++ b/terraform/cos-dev/applications.tf @@ -1,5 +1,6 @@ module "alertmanager" { source = "git::https://github.com/canonical/alertmanager-k8s-operator//terraform" + app_name = var.alertmanager.app_name channel = local.channels.alertmanager config = var.alertmanager.config @@ -12,6 +13,7 @@ module "alertmanager" { module "catalogue" { source = "git::https://github.com/canonical/catalogue-k8s-operator//terraform" + app_name = var.catalogue.app_name channel = local.channels.catalogue config = var.catalogue.config @@ -24,6 +26,7 @@ module "catalogue" { module "grafana" { source = "git::https://github.com/canonical/grafana-k8s-operator//terraform" + app_name = var.grafana.app_name channel = local.channels.grafana config = var.grafana.config @@ -63,6 +66,7 @@ module "istio_beacon" { module "loki_coordinator" { source = "git::https://github.com/canonical/loki-operators//coordinator/terraform" + app_name = var.loki_coordinator.app_name channel = local.channels.loki config = var.loki_coordinator.config From 9462ac20fb4f62826efccf8c2f84390b4e797d5d Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Fri, 22 May 2026 14:00:49 -0400 Subject: [PATCH 19/21] chore --- terraform/cos-dev/README.md | 2 +- terraform/cos-dev/applications.tf | 6 ++- terraform/cos-dev/integrations.tf | 2 +- terraform/cos-dev/locals.tf | 4 +- terraform/cos-dev/offers.tf | 41 ++++++++++++------- .../tests/conditional_ingress.tftest.hcl | 4 +- .../cos-dev/tests/external_tls.tftest.hcl | 24 ----------- terraform/cos-dev/tests/mesh.tftest.hcl | 28 ++++++++++++- terraform/cos-dev/variables.tf | 6 +-- 9 files changed, 66 insertions(+), 51 deletions(-) diff --git a/terraform/cos-dev/README.md b/terraform/cos-dev/README.md index 8a5d76d5..a2429e70 100644 --- a/terraform/cos-dev/README.md +++ b/terraform/cos-dev/README.md @@ -61,7 +61,7 @@ This module is intended for development and testing environments where full HA i | [loki\_bucket](#input\_loki\_bucket) | Loki S3 bucket name | `string` | `"loki"` | no | | [loki\_coordinator](#input\_loki\_coordinator) | Application configuration for the Loki coordinator. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "loki")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [loki\_worker](#input\_loki\_worker) | Application configuration for the Loki worker(s). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "loki-worker")
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
# Monolithic mode (role-all)
config = optional(map(string), {})
storage_directives = optional(map(string), {})
units = optional(number, 1)
# Distributed mode
backend_config = optional(map(string), {})
read_config = optional(map(string), {})
write_config = optional(map(string), {})
backend_storage_directives = optional(map(string), {})
read_storage_directives = optional(map(string), {})
write_storage_directives = optional(map(string), {})
backend_units = optional(number, 1)
read_units = optional(number, 1)
write_units = optional(number, 1)
})
| `{}` | no | -| [mesh\_enabled](#input\_mesh\_enabled) | Configure the service mesh. | `bool` | `false` | no | +| [mesh\_enabled](#input\_mesh\_enabled) | Configure the service mesh. | `bool` | `false` | no | | [mimir\_bucket](#input\_mimir\_bucket) | Mimir S3 bucket name | `string` | `"mimir"` | no | | [mimir\_coordinator](#input\_mimir\_coordinator) | Application configuration for the Mimir coordinator. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "mimir")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
| `{}` | no | | [mimir\_worker](#input\_mimir\_worker) | Application configuration for the Mimir worker(s). For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application |
object({
app_name = optional(string, "mimir-worker")
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
# Monolithic mode (role-all)
config = optional(map(string), {})
storage_directives = optional(map(string), {})
units = optional(number, 1)
# Distributed mode
backend_config = optional(map(string), {})
read_config = optional(map(string), {})
write_config = optional(map(string), {})
backend_storage_directives = optional(map(string), {})
read_storage_directives = optional(map(string), {})
write_storage_directives = optional(map(string), {})
backend_units = optional(number, 1)
read_units = optional(number, 1)
write_units = optional(number, 1)
})
| `{}` | no | diff --git a/terraform/cos-dev/applications.tf b/terraform/cos-dev/applications.tf index cdf32467..ec8de9ae 100644 --- a/terraform/cos-dev/applications.tf +++ b/terraform/cos-dev/applications.tf @@ -39,8 +39,9 @@ module "grafana" { } module "istio_ingress" { - count = local.istio_ingress_enabled ? 1 : 0 source = "git::https://github.com/canonical/istio-ingress-k8s-operator//terraform" + count = local.istio_ingress_enabled ? 1 : 0 + app_name = var.istio_ingress.app_name channel = local.channels.istio_ingress config = var.istio_ingress.config @@ -52,8 +53,9 @@ module "istio_ingress" { } module "istio_beacon" { - count = var.mesh_enabled ? 1 : 0 source = "git::https://github.com/canonical/istio-beacon-k8s-operator//terraform" + count = var.service_mesh ? 1 : 0 + app_name = var.istio_beacon.app_name channel = local.channels.istio_beacon config = var.istio_beacon.config diff --git a/terraform/cos-dev/integrations.tf b/terraform/cos-dev/integrations.tf index cd604ebe..6c87dcf8 100644 --- a/terraform/cos-dev/integrations.tf +++ b/terraform/cos-dev/integrations.tf @@ -961,7 +961,7 @@ resource "juju_integration" "traces_and_metrics_correlation" { # -------------- # Service Mesh --------------------- resource "juju_integration" "istio_beacon" { - for_each = var.mesh_enabled ? { + for_each = var.service_mesh ? { alertmanager = { app_name = module.alertmanager.app_name endpoint = module.alertmanager.requires.service_mesh diff --git a/terraform/cos-dev/locals.tf b/terraform/cos-dev/locals.tf index 32d6ffe9..63713a71 100644 --- a/terraform/cos-dev/locals.tf +++ b/terraform/cos-dev/locals.tf @@ -1,8 +1,8 @@ locals { tls_termination = var.external_certificates_offer_url != null ? true : false reverse_proxy_enabled = anytrue(values(var.ingress)) - traefik_enabled = local.reverse_proxy_enabled && !var.mesh_enabled - istio_ingress_enabled = local.reverse_proxy_enabled && var.mesh_enabled + traefik_enabled = local.reverse_proxy_enabled && !var.service_mesh + istio_ingress_enabled = local.reverse_proxy_enabled && var.service_mesh traefik_base = "ubuntu@20.04" tracks = { alertmanager = "dev" diff --git a/terraform/cos-dev/offers.tf b/terraform/cos-dev/offers.tf index f925b10a..aae2862b 100644 --- a/terraform/cos-dev/offers.tf +++ b/terraform/cos-dev/offers.tf @@ -38,7 +38,8 @@ resource "juju_offer" "tempo_tracing" { # -------------- # CMR Mesh offers -------------- # resource "juju_offer" "alertmanager_provide_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 + name = "alertmanager-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.alertmanager.app_name @@ -46,7 +47,8 @@ resource "juju_offer" "alertmanager_provide_cmr_mesh" { } resource "juju_offer" "alertmanager_require_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 + name = "alertmanager-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.alertmanager.app_name @@ -54,7 +56,8 @@ resource "juju_offer" "alertmanager_require_cmr_mesh" { } resource "juju_offer" "catalogue_provide_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 + name = "catalogue-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.catalogue.app_name @@ -62,7 +65,8 @@ resource "juju_offer" "catalogue_provide_cmr_mesh" { } resource "juju_offer" "catalogue_require_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 + name = "catalogue-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.catalogue.app_name @@ -70,7 +74,8 @@ resource "juju_offer" "catalogue_require_cmr_mesh" { } resource "juju_offer" "grafana_provide_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 + name = "grafana-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.grafana.app_name @@ -80,7 +85,8 @@ resource "juju_offer" "grafana_provide_cmr_mesh" { } resource "juju_offer" "grafana_require_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 + name = "grafana-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.grafana.app_name @@ -90,7 +96,8 @@ resource "juju_offer" "grafana_require_cmr_mesh" { } resource "juju_offer" "loki_provide_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 + name = "loki-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.loki_coordinator.app_name @@ -98,7 +105,8 @@ resource "juju_offer" "loki_provide_cmr_mesh" { } resource "juju_offer" "loki_require_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 + name = "loki-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.loki_coordinator.app_name @@ -106,7 +114,8 @@ resource "juju_offer" "loki_require_cmr_mesh" { } resource "juju_offer" "mimir_provide_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 + name = "mimir-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.mimir_coordinator.app_name @@ -114,7 +123,7 @@ resource "juju_offer" "mimir_provide_cmr_mesh" { } resource "juju_offer" "mimir_require_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 name = "mimir-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.mimir_coordinator.app_name @@ -122,7 +131,8 @@ resource "juju_offer" "mimir_require_cmr_mesh" { } resource "juju_offer" "opentelemetry_collector_provide_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 + name = "opentelemetry-collector-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.opentelemetry_collector.app_name @@ -130,7 +140,8 @@ resource "juju_offer" "opentelemetry_collector_provide_cmr_mesh" { } resource "juju_offer" "opentelemetry_collector_require_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 + name = "opentelemetry-collector-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.opentelemetry_collector.app_name @@ -138,7 +149,8 @@ resource "juju_offer" "opentelemetry_collector_require_cmr_mesh" { } resource "juju_offer" "tempo_provide_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 + name = "tempo-provide-cmr-mesh" model_uuid = var.model_uuid application_name = module.tempo_coordinator.app_name @@ -146,7 +158,8 @@ resource "juju_offer" "tempo_provide_cmr_mesh" { } resource "juju_offer" "tempo_require_cmr_mesh" { - count = var.mesh_enabled ? 1 : 0 + count = var.service_mesh ? 1 : 0 + name = "tempo-require-cmr-mesh" model_uuid = var.model_uuid application_name = module.tempo_coordinator.app_name diff --git a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl index 8f48d014..5e4e363e 100644 --- a/terraform/cos-dev/tests/conditional_ingress.tftest.hcl +++ b/terraform/cos-dev/tests/conditional_ingress.tftest.hcl @@ -73,7 +73,7 @@ run "istio_ingress_enabled" { variables { internal_tls = false - mesh_enabled = true + service_mesh = true } assert { @@ -104,7 +104,7 @@ run "istio_ingress_disabled" { variables { internal_tls = false - mesh_enabled = true + service_mesh = true ingress = { alertmanager = false catalogue = false diff --git a/terraform/cos-dev/tests/external_tls.tftest.hcl b/terraform/cos-dev/tests/external_tls.tftest.hcl index 81f7f5d9..e7833149 100644 --- a/terraform/cos-dev/tests/external_tls.tftest.hcl +++ b/terraform/cos-dev/tests/external_tls.tftest.hcl @@ -2,30 +2,6 @@ mock_provider "juju" {} variables { model_uuid = "00000000-0000-0000-0000-000000000000" } -# --- mesh and internal_tls disabled: no validation error --- - -run "mesh_and_internal_tls_disabled" { - command = plan - - variables { - mesh_enabled = false - internal_tls = false - } -} - -# --- mesh and internal_tls enabled: validation error --- - -run "mesh_and_internal_tls_enabled_fails" { - command = plan - - variables { - mesh_enabled = true - internal_tls = true - } - - expect_failures = [var.mesh_enabled] -} - # --- external cert URLs both null: no validation error --- run "external_cert_urls_both_null" { diff --git a/terraform/cos-dev/tests/mesh.tftest.hcl b/terraform/cos-dev/tests/mesh.tftest.hcl index 2e080b5a..5450ca54 100644 --- a/terraform/cos-dev/tests/mesh.tftest.hcl +++ b/terraform/cos-dev/tests/mesh.tftest.hcl @@ -2,6 +2,30 @@ mock_provider "juju" {} variables { model_uuid = "00000000-0000-0000-0000-000000000000" } +# --- mesh and internal_tls disabled: no validation error --- + +run "mesh_and_internal_tls_disabled" { + command = plan + + variables { + service_mesh = false + internal_tls = false + } +} + +# --- mesh and internal_tls enabled: validation error --- + +run "mesh_and_internal_tls_enabled_fails" { + command = plan + + variables { + service_mesh = true + internal_tls = true + } + + expect_failures = [var.service_mesh] +} + # --- default: mesh disabled - no ingress via istio --- run "mesh_disabled" { @@ -40,11 +64,11 @@ run "mesh_disabled" { # --- mesh enabled: ingress via istio --- -run "mesh_enabled" { +run "service_mesh" { command = plan variables { - mesh_enabled = true + service_mesh = true internal_tls = false } diff --git a/terraform/cos-dev/variables.tf b/terraform/cos-dev/variables.tf index 37b12149..ce5aca41 100644 --- a/terraform/cos-dev/variables.tf +++ b/terraform/cos-dev/variables.tf @@ -48,14 +48,14 @@ variable "storage_backend" { # -------------- # Network configurations -------------- -variable "mesh_enabled" { +variable "service_mesh" { description = "Configure the service mesh." type = bool default = false validation { - condition = !(var.internal_tls && var.mesh_enabled) - error_message = "mesh_enabled and internal_tls cannot both be enabled at the same time." + condition = !(var.internal_tls && var.service_mesh) + error_message = "service_mesh and internal_tls cannot both be enabled at the same time." } } From 0e842198f2c65420733cb52c54f613af6b09f17e Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Fri, 22 May 2026 14:01:25 -0400 Subject: [PATCH 20/21] chore --- terraform/cos-dev/offers.tf | 131 ----------------------------------- terraform/cos-dev/outputs.tf | 16 ----- 2 files changed, 147 deletions(-) diff --git a/terraform/cos-dev/offers.tf b/terraform/cos-dev/offers.tf index aae2862b..a5937c88 100644 --- a/terraform/cos-dev/offers.tf +++ b/terraform/cos-dev/offers.tf @@ -34,134 +34,3 @@ resource "juju_offer" "tempo_tracing" { application_name = module.tempo_coordinator.app_name endpoints = [module.tempo_coordinator.provides.tracing] } - -# -------------- # CMR Mesh offers -------------- # - -resource "juju_offer" "alertmanager_provide_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - - name = "alertmanager-provide-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.alertmanager.app_name - endpoints = [module.alertmanager.provides.provide_cmr_mesh] -} - -resource "juju_offer" "alertmanager_require_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - - name = "alertmanager-require-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.alertmanager.app_name - endpoints = [module.alertmanager.requires.require_cmr_mesh] -} - -resource "juju_offer" "catalogue_provide_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - - name = "catalogue-provide-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.catalogue.app_name - endpoints = [module.catalogue.provides.provide_cmr_mesh] -} - -resource "juju_offer" "catalogue_require_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - - name = "catalogue-require-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.catalogue.app_name - endpoints = [module.catalogue.requires.require_cmr_mesh] -} - -resource "juju_offer" "grafana_provide_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - - name = "grafana-provide-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.grafana.app_name - endpoints = [module.grafana.provides.provide_cmr_mesh] - - lifecycle { replace_triggered_by = [terraform_data.grafana_litestream_resource] } -} - -resource "juju_offer" "grafana_require_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - - name = "grafana-require-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.grafana.app_name - endpoints = [module.grafana.requires.require_cmr_mesh] - - lifecycle { replace_triggered_by = [terraform_data.grafana_litestream_resource] } -} - -resource "juju_offer" "loki_provide_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - - name = "loki-provide-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.loki_coordinator.app_name - endpoints = [module.loki_coordinator.provides.provide_cmr_mesh] -} - -resource "juju_offer" "loki_require_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - - name = "loki-require-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.loki_coordinator.app_name - endpoints = [module.loki_coordinator.requires.require_cmr_mesh] -} - -resource "juju_offer" "mimir_provide_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - - name = "mimir-provide-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.mimir_coordinator.app_name - endpoints = [module.mimir_coordinator.provides.provide_cmr_mesh] -} - -resource "juju_offer" "mimir_require_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - name = "mimir-require-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.mimir_coordinator.app_name - endpoints = [module.mimir_coordinator.requires.require_cmr_mesh] -} - -resource "juju_offer" "opentelemetry_collector_provide_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - - name = "opentelemetry-collector-provide-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.opentelemetry_collector.app_name - endpoints = [module.opentelemetry_collector.provides.provide_cmr_mesh] -} - -resource "juju_offer" "opentelemetry_collector_require_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - - name = "opentelemetry-collector-require-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.opentelemetry_collector.app_name - endpoints = [module.opentelemetry_collector.requires.require_cmr_mesh] -} - -resource "juju_offer" "tempo_provide_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - - name = "tempo-provide-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.tempo_coordinator.app_name - endpoints = [module.tempo_coordinator.provides.provide_cmr_mesh] -} - -resource "juju_offer" "tempo_require_cmr_mesh" { - count = var.service_mesh ? 1 : 0 - - name = "tempo-require-cmr-mesh" - model_uuid = var.model_uuid - application_name = module.tempo_coordinator.app_name - endpoints = [module.tempo_coordinator.requires.require_cmr_mesh] -} diff --git a/terraform/cos-dev/outputs.tf b/terraform/cos-dev/outputs.tf index 30adf497..060ee89a 100644 --- a/terraform/cos-dev/outputs.tf +++ b/terraform/cos-dev/outputs.tf @@ -7,22 +7,6 @@ output "offers" { loki_logging = juju_offer.loki_logging mimir_receive_remote_write = juju_offer.mimir_receive_remote_write tempo_tracing = juju_offer.tempo_tracing - - # CMR Mesh - alertmanager_provide_cmr_mesh = try(juju_offer.alertmanager_provide_cmr_mesh[0], null) - alertmanager_require_cmr_mesh = try(juju_offer.alertmanager_require_cmr_mesh[0], null) - catalogue_provide_cmr_mesh = try(juju_offer.catalogue_provide_cmr_mesh[0], null) - catalogue_require_cmr_mesh = try(juju_offer.catalogue_require_cmr_mesh[0], null) - grafana_provide_cmr_mesh = try(juju_offer.grafana_provide_cmr_mesh[0], null) - grafana_require_cmr_mesh = try(juju_offer.grafana_require_cmr_mesh[0], null) - loki_provide_cmr_mesh = try(juju_offer.loki_provide_cmr_mesh[0], null) - loki_require_cmr_mesh = try(juju_offer.loki_require_cmr_mesh[0], null) - mimir_provide_cmr_mesh = try(juju_offer.mimir_provide_cmr_mesh[0], null) - mimir_require_cmr_mesh = try(juju_offer.mimir_require_cmr_mesh[0], null) - opentelemetry_collector_provide_cmr_mesh = try(juju_offer.opentelemetry_collector_provide_cmr_mesh[0], null) - opentelemetry_collector_require_cmr_mesh = try(juju_offer.opentelemetry_collector_require_cmr_mesh[0], null) - tempo_provide_cmr_mesh = try(juju_offer.tempo_provide_cmr_mesh[0], null) - tempo_require_cmr_mesh = try(juju_offer.tempo_require_cmr_mesh[0], null) } description = "All Juju offers which are exposed by this product module" } From 4ed8950db9566ecd70271b4250080c255d6f7f90 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Fri, 22 May 2026 14:14:03 -0400 Subject: [PATCH 21/21] chore --- terraform/cos-dev/tests/mesh.tftest.hcl | 43 +------------------------ 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/terraform/cos-dev/tests/mesh.tftest.hcl b/terraform/cos-dev/tests/mesh.tftest.hcl index 5450ca54..372857fb 100644 --- a/terraform/cos-dev/tests/mesh.tftest.hcl +++ b/terraform/cos-dev/tests/mesh.tftest.hcl @@ -26,7 +26,7 @@ run "mesh_and_internal_tls_enabled_fails" { expect_failures = [var.service_mesh] } -# --- default: mesh disabled - no ingress via istio --- +# --- default: mesh disabled - no istio beacon --- run "mesh_disabled" { command = plan @@ -36,30 +36,10 @@ run "mesh_disabled" { error_message = "Expected no istio_beacon module when the mesh is disabled" } - assert { - condition = length(module.istio_ingress) == 0 - error_message = "Expected no istio_ingress module when the mesh is disabled" - } - assert { condition = length(juju_integration.istio_beacon) == 0 error_message = "Expected no istio_beacon integrations when the mesh is disabled" } - - assert { - condition = length(juju_integration.istio_ingress) == 0 - error_message = "Expected no istio_ingress integrations when the mesh is disabled" - } - - assert { - condition = length(juju_integration.grafana_istio_ingress) == 0 - error_message = "Expected no grafana_istio_ingress integrations when the mesh is disabled" - } - - assert { - condition = length(juju_integration.istio_ingress_route) == 0 - error_message = "Expected no istio_ingress_route integrations when the mesh is disabled" - } } # --- mesh enabled: ingress via istio --- @@ -77,29 +57,8 @@ run "service_mesh" { error_message = "Expected istio_beacon module when the mesh is enabled" } - assert { - condition = length(module.istio_ingress) == 1 - error_message = "Expected istio_ingress module when the mesh is enabled" - } - assert { condition = length(juju_integration.istio_beacon) == 7 error_message = "Unexpected istio_beacon integrations when the mesh is enabled" } - - assert { - condition = length(juju_integration.istio_ingress) == 4 - error_message = "Unexpected istio_ingress integrations when the mesh is enabled" - } - - assert { - condition = length(juju_integration.grafana_istio_ingress) == 1 - error_message = "Unexpected grafana_istio_ingress integrations when the mesh is enabled" - } - - assert { - condition = length(juju_integration.istio_ingress_route) == 2 - error_message = "Unexpected istio_ingress_route integrations when the mesh is enabled" - - } }