diff --git a/terraform/cos-lite/README.md b/terraform/cos-lite/README.md index 404bc531..e1e3b4ac 100644 --- a/terraform/cos-lite/README.md +++ b/terraform/cos-lite/README.md @@ -26,8 +26,8 @@ This is a Terraform module facilitating the deployment of the COS Lite solution, | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [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@22.04. Changing this value for machine charms will trigger a replace by terraform. 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 |
-| [channel](#input\_channel) | Channel that the applications are (unless overwritten by individual channels) deployed from | `string` | `"dev/edge"` | 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 (e.g. admin/external-ca.certificates) 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 |
@@ -36,6 +36,7 @@ This is a Terraform module facilitating the deployment of the COS Lite solution,
| [loki](#input\_loki) | Application configuration for Loki. 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 |
| [model\_uuid](#input\_model\_uuid) | Reference to an existing model resource or data source for the model to deploy to | `string` | n/a | yes |
| [prometheus](#input\_prometheus) | Application configuration for Prometheus. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application | object({
app_name = optional(string, "prometheus")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
}) | `{}` | no |
+| [risk](#input\_risk) | Risk level that the applications are (unless overwritten by individual channels) deployed from | `string` | `"edge"` | 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")
channel = optional(string, "1/stable")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
}) | `{}` | no |
| [traefik](#input\_traefik) | Application configuration for Traefik. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application | object({
app_name = optional(string, "traefik")
channel = optional(string, "latest/stable")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
}) | `{}` | no |
diff --git a/terraform/cos-lite/applications.tf b/terraform/cos-lite/applications.tf
index dd1bd5ab..72d34dd1 100644
--- a/terraform/cos-lite/applications.tf
+++ b/terraform/cos-lite/applications.tf
@@ -1,11 +1,11 @@
module "alertmanager" {
source = "git::https://github.com/canonical/alertmanager-k8s-operator//terraform"
app_name = var.alertmanager.app_name
- channel = var.channel
+ channel = local.channels.alertmanager
config = var.alertmanager.config
constraints = var.alertmanager.constraints
model_uuid = var.model_uuid
- revision = var.alertmanager.revision
+ revision = local.revisions.alertmanager
storage_directives = var.alertmanager.storage_directives
units = var.alertmanager.units
}
@@ -13,11 +13,11 @@ module "alertmanager" {
module "catalogue" {
source = "git::https://github.com/canonical/catalogue-k8s-operator//terraform"
app_name = var.catalogue.app_name
- channel = var.channel
+ channel = local.channels.catalogue
config = var.catalogue.config
constraints = var.catalogue.constraints
model_uuid = var.model_uuid
- revision = var.catalogue.revision
+ revision = local.revisions.catalogue
storage_directives = var.catalogue.storage_directives
units = var.catalogue.units
}
@@ -25,11 +25,11 @@ module "catalogue" {
module "grafana" {
source = "git::https://github.com/canonical/grafana-k8s-operator//terraform"
app_name = var.grafana.app_name
- channel = var.channel
+ channel = local.channels.grafana
config = var.grafana.config
constraints = var.grafana.constraints
model_uuid = var.model_uuid
- revision = var.grafana.revision
+ revision = local.revisions.grafana
storage_directives = var.grafana.storage_directives
units = var.grafana.units
}
@@ -37,24 +37,24 @@ module "grafana" {
module "loki" {
source = "git::https://github.com/canonical/loki-k8s-operator//terraform"
app_name = var.loki.app_name
- channel = var.channel
+ channel = local.channels.loki
config = var.loki.config
constraints = var.loki.constraints
model_uuid = var.model_uuid
storage_directives = var.loki.storage_directives
- revision = var.loki.revision
+ revision = local.revisions.loki
units = var.loki.units
}
module "prometheus" {
source = "git::https://github.com/canonical/prometheus-k8s-operator//terraform"
app_name = var.prometheus.app_name
- channel = var.channel
+ channel = local.channels.prometheus
config = var.prometheus.config
constraints = var.prometheus.constraints
model_uuid = var.model_uuid
storage_directives = var.prometheus.storage_directives
- revision = var.prometheus.revision
+ revision = local.revisions.prometheus
units = var.prometheus.units
}
@@ -62,22 +62,22 @@ module "ssc" {
count = var.internal_tls ? 1 : 0
source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform"
app_name = var.ssc.app_name
- channel = var.ssc.channel
+ channel = local.channels.ssc
config = var.ssc.config
constraints = var.ssc.constraints
model_uuid = var.model_uuid
- revision = var.ssc.revision
+ revision = local.revisions.ssc
units = var.ssc.units
}
module "traefik" {
source = "git::https://github.com/canonical/traefik-k8s-operator//terraform"
app_name = var.traefik.app_name
- channel = var.traefik.channel
+ channel = local.channels.traefik
config = var.traefik.config
constraints = var.traefik.constraints
model_uuid = var.model_uuid
- revision = var.traefik.revision
+ revision = local.revisions.traefik
storage_directives = var.traefik.storage_directives
units = var.traefik.units
}
diff --git a/terraform/cos-lite/locals.tf b/terraform/cos-lite/locals.tf
new file mode 100644
index 00000000..e1204dd0
--- /dev/null
+++ b/terraform/cos-lite/locals.tf
@@ -0,0 +1,30 @@
+locals {
+ tls_termination = var.external_certificates_offer_url != null ? true : false
+ tracks = {
+ alertmanager = "0.31"
+ catalogue = "3.0"
+ grafana = "12.4"
+ loki = "3.7"
+ prometheus = "3.10"
+ ssc = "latest"
+ traefik = "latest"
+ }
+ channels = {
+ alertmanager = "${local.tracks.alertmanager}/${var.risk}"
+ catalogue = "${local.tracks.catalogue}/${var.risk}"
+ grafana = "${local.tracks.grafana}/${var.risk}"
+ loki = "${local.tracks.loki}/${var.risk}"
+ prometheus = "${local.tracks.prometheus}/${var.risk}"
+ ssc = "${local.tracks.ssc}/${var.risk}"
+ traefik = "${local.tracks.traefik}/${var.risk}"
+ }
+ revisions = {
+ 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
+ loki = var.loki.revision != null ? var.loki.revision : data.juju_charm.loki_info.revision
+ prometheus = var.prometheus.revision != null ? var.prometheus.revision : data.juju_charm.prometheus_info.revision
+ ssc = var.ssc.revision != null ? var.ssc.revision : data.juju_charm.ssc_info.revision
+ traefik = var.traefik.revision != null ? var.traefik.revision : data.juju_charm.traefik_info.revision
+ }
+}
\ No newline at end of file
diff --git a/terraform/cos-lite/tests/revision_pin.tftest.hcl b/terraform/cos-lite/tests/revision_pin.tftest.hcl
new file mode 100644
index 00000000..82c61e73
--- /dev/null
+++ b/terraform/cos-lite/tests/revision_pin.tftest.hcl
@@ -0,0 +1,95 @@
+mock_provider "juju" {}
+
+variables { model_uuid = "00000000-0000-0000-0000-000000000000" }
+
+# --- User revision pin is respected and not overridden by juju_charm datasource ---
+
+run "user_revision_pin_is_respected" {
+ command = plan
+
+ variables {
+ alertmanager = { revision = 1 }
+ catalogue = { revision = 2 }
+ grafana = { revision = 3 }
+ loki = { revision = 4 }
+ prometheus = { revision = 5 }
+ ssc = { revision = 6 }
+ traefik = { revision = 7 }
+ }
+
+ assert {
+ condition = local.revisions.alertmanager == 1
+ error_message = "Expected alertmanager revision 1, got ${local.revisions.alertmanager}"
+ }
+
+ assert {
+ condition = local.revisions.catalogue == 2
+ error_message = "Expected catalogue revision 2, got ${local.revisions.catalogue}"
+ }
+
+ assert {
+ condition = local.revisions.grafana == 3
+ error_message = "Expected grafana revision 3, got ${local.revisions.grafana}"
+ }
+
+ assert {
+ condition = local.revisions.loki == 4
+ error_message = "Expected loki revision 4, got ${local.revisions.loki}"
+ }
+
+ assert {
+ condition = local.revisions.prometheus == 5
+ error_message = "Expected prometheus revision 5, got ${local.revisions.prometheus}"
+ }
+
+ assert {
+ condition = local.revisions.ssc == 6
+ error_message = "Expected ssc revision 6, got ${local.revisions.ssc}"
+ }
+
+ assert {
+ condition = local.revisions.traefik == 7
+ error_message = "Expected traefik revision 7, got ${local.revisions.traefik}"
+ }
+}
+
+# --- Without a revision pin, the juju_charm datasource determines the revision ---
+
+run "no_pin_uses_datasource" {
+ command = plan
+
+ assert {
+ condition = local.revisions.alertmanager == data.juju_charm.alertmanager_info.revision
+ error_message = "alertmanager revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.catalogue == data.juju_charm.catalogue_info.revision
+ error_message = "catalogue revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.grafana == data.juju_charm.grafana_info.revision
+ error_message = "grafana revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.loki == data.juju_charm.loki_info.revision
+ error_message = "loki revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.prometheus == data.juju_charm.prometheus_info.revision
+ error_message = "prometheus revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.ssc == data.juju_charm.ssc_info.revision
+ error_message = "ssc revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.traefik == data.juju_charm.traefik_info.revision
+ error_message = "traefik revision should come from datasource when no pin is set"
+ }
+}
diff --git a/terraform/cos-lite/upgrades.tf b/terraform/cos-lite/upgrades.tf
new file mode 100644
index 00000000..1d1d0587
--- /dev/null
+++ b/terraform/cos-lite/upgrades.tf
@@ -0,0 +1,43 @@
+# -------------- # CharmHub API -------------- #
+
+data "juju_charm" "alertmanager_info" {
+ charm = "alertmanager-k8s"
+ channel = local.channels.alertmanager
+ base = var.base
+}
+
+data "juju_charm" "catalogue_info" {
+ charm = "catalogue-k8s"
+ channel = local.channels.catalogue
+ base = var.base
+}
+
+data "juju_charm" "grafana_info" {
+ charm = "grafana-k8s"
+ channel = local.channels.grafana
+ base = var.base
+}
+
+data "juju_charm" "loki_info" {
+ charm = "loki-k8s"
+ channel = local.channels.loki
+ base = var.base
+}
+
+data "juju_charm" "prometheus_info" {
+ charm = "prometheus-k8s"
+ channel = local.channels.prometheus
+ base = var.base
+}
+
+data "juju_charm" "ssc_info" {
+ charm = "self-signed-certificates"
+ channel = local.channels.ssc
+ base = var.base
+}
+
+data "juju_charm" "traefik_info" {
+ charm = "traefik-k8s"
+ channel = local.channels.traefik
+ base = var.base
+}
diff --git a/terraform/cos-lite/variables.tf b/terraform/cos-lite/variables.tf
index 5421f6c6..40176124 100644
--- a/terraform/cos-lite/variables.tf
+++ b/terraform/cos-lite/variables.tf
@@ -5,15 +5,16 @@
# causes the operation to fail due to https://github.com/juju/terraform-provider-juju/issues/344
# Therefore, we set a default value of "arch=amd64" for all applications.
-locals {
- # https://github.com/juju/terraform-provider-juju/issues/972
- tls_termination = var.external_certificates_offer_url != null ? true : false
+variable "risk" {
+ description = "Risk level that the applications are (unless overwritten by individual channels) deployed from"
+ type = string
+ default = "edge"
}
-variable "channel" {
- description = "Channel that the applications are (unless overwritten by individual channels) deployed from"
+variable "base" {
+ description = "The operating system on which to deploy. E.g. ubuntu@22.04. Changing this value for machine charms will trigger a replace by terraform. Check Charmhub for per-charm base support."
+ default = "ubuntu@24.04"
type = string
- default = "dev/edge"
}
variable "model_uuid" {
diff --git a/terraform/cos/README.md b/terraform/cos/README.md
index bd294b6a..e9ab7ac8 100644
--- a/terraform/cos/README.md
+++ b/terraform/cos/README.md
@@ -32,8 +32,8 @@ This is a Terraform module facilitating the deployment of the COS solution, usin
|------|-------------|------|---------|:--------:|
| [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 |
| [anti\_affinity](#input\_anti\_affinity) | Enable anti-affinity constraints across all HA modules (Mimir, Loki, Tempo) | `bool` | `true` | no |
+| [base](#input\_base) | The operating system on which to deploy. E.g. ubuntu@22.04. Changing this value for machine charms will trigger a replace by terraform. 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 |
-| [channel](#input\_channel) | Channel that the applications are (unless overwritten by individual channels) deployed from | `string` | `"dev/edge"` | no |
| [cloud](#input\_cloud) | Kubernetes cloud or environment where this COS module will be deployed (e.g self-managed, aws) | `string` | `"self-managed"` | 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 |
@@ -48,6 +48,7 @@ This is a Terraform module facilitating the deployment of the COS solution, usin
| [mimir\_worker](#input\_mimir\_worker) | Application configuration for all Mimir Workers. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application | object({
backend_config = optional(map(string), {})
read_config = optional(map(string), {})
write_config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
backend_storage_directives = optional(map(string), {})
read_storage_directives = optional(map(string), {})
write_storage_directives = optional(map(string), {})
backend_units = optional(number, 3)
read_units = optional(number, 3)
write_units = optional(number, 3)
}) | `{}` | 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 |
+| [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 | `string` | n/a | yes |
| [s3\_endpoint](#input\_s3\_endpoint) | S3 endpoint | `string` | n/a | yes |
| [s3\_integrator](#input\_s3\_integrator) | Application configuration for all S3-integrators in coordinated workers. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application | object({
channel = optional(string, "2/edge")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
}) | `{}` | no |
diff --git a/terraform/cos/applications.tf b/terraform/cos/applications.tf
index 8420484e..deaec9aa 100644
--- a/terraform/cos/applications.tf
+++ b/terraform/cos/applications.tf
@@ -1,11 +1,11 @@
module "alertmanager" {
source = "git::https://github.com/canonical/alertmanager-k8s-operator//terraform"
app_name = var.alertmanager.app_name
- channel = var.channel
+ channel = local.channels.alertmanager
config = var.alertmanager.config
constraints = var.alertmanager.constraints
model_uuid = var.model_uuid
- revision = var.alertmanager.revision
+ revision = local.revisions.alertmanager
storage_directives = var.alertmanager.storage_directives
units = var.alertmanager.units
}
@@ -13,11 +13,11 @@ module "alertmanager" {
module "catalogue" {
source = "git::https://github.com/canonical/catalogue-k8s-operator//terraform"
app_name = var.catalogue.app_name
- channel = var.channel
+ channel = local.channels.catalogue
config = var.catalogue.config
constraints = var.catalogue.constraints
model_uuid = var.model_uuid
- revision = var.catalogue.revision
+ revision = local.revisions.catalogue
storage_directives = var.catalogue.storage_directives
units = var.catalogue.units
}
@@ -25,11 +25,11 @@ module "catalogue" {
module "grafana" {
source = "git::https://github.com/canonical/grafana-k8s-operator//terraform"
app_name = var.grafana.app_name
- channel = var.channel
+ channel = local.channels.grafana
config = var.grafana.config
constraints = var.grafana.constraints
model_uuid = var.model_uuid
- revision = var.grafana.revision
+ revision = local.revisions.grafana
storage_directives = var.grafana.storage_directives
units = var.grafana.units
}
@@ -37,28 +37,28 @@ module "grafana" {
module "loki" {
source = "git::https://github.com/canonical/loki-operators//terraform"
anti_affinity = var.anti_affinity
- channel = var.channel
+ channel = local.channels.loki
model_uuid = var.model_uuid
s3_endpoint = var.s3_endpoint
s3_secret_key = var.s3_secret_key
s3_access_key = var.s3_access_key
s3_bucket = var.loki_bucket
- s3_integrator_channel = var.s3_integrator.channel
+ s3_integrator_channel = local.channels.s3_integrator
s3_integrator_config = var.s3_integrator.config
s3_integrator_constraints = var.s3_integrator.constraints
- s3_integrator_revision = var.s3_integrator.revision
+ s3_integrator_revision = local.revisions.s3_integrator
s3_integrator_storage_directives = var.s3_integrator.storage_directives
s3_integrator_units = var.s3_integrator.units
coordinator_config = var.loki_coordinator.config
coordinator_constraints = var.loki_coordinator.constraints
- coordinator_revision = var.loki_coordinator.revision
+ coordinator_revision = local.revisions.loki_coordinator
coordinator_storage_directives = var.loki_coordinator.storage_directives
coordinator_units = var.loki_coordinator.units
backend_config = var.loki_worker.backend_config
read_config = var.loki_worker.read_config
write_config = var.loki_worker.write_config
worker_constraints = var.loki_worker.constraints
- worker_revision = var.loki_worker.revision
+ worker_revision = local.revisions.loki_worker
backend_worker_storage_directives = var.loki_worker.backend_storage_directives
read_worker_storage_directives = var.loki_worker.read_storage_directives
write_worker_storage_directives = var.loki_worker.write_storage_directives
@@ -68,38 +68,30 @@ module "loki" {
}
module "mimir" {
- source = "git::https://github.com/canonical/mimir-operators//terraform"
- anti_affinity = var.anti_affinity
- channel = var.channel
- model_uuid = var.model_uuid
- s3_endpoint = var.s3_endpoint
- s3_secret_key = var.s3_secret_key
- s3_access_key = var.s3_access_key
- s3_bucket = var.mimir_bucket
- s3_integrator_channel = var.s3_integrator.channel
- s3_integrator_config = var.s3_integrator.config
- s3_integrator_constraints = var.s3_integrator.constraints
- s3_integrator_revision = var.s3_integrator.revision
- s3_integrator_storage_directives = var.s3_integrator.storage_directives
- s3_integrator_units = var.s3_integrator.units
- coordinator_config = merge(
- var.mimir_coordinator.config,
- # enable exemplar storage (required for metrics-to-traces).
- # This config option is not supported in track `1`, so we'll set it only
- # for newer tracks to maintain backward compatibility.
- can(regex("^1/", var.channel)) ? {} : {
- "max_global_exemplars_per_user" = "100000"
- }
- )
+ source = "git::https://github.com/canonical/mimir-operators//terraform"
+ anti_affinity = var.anti_affinity
+ channel = local.channels.mimir
+ model_uuid = var.model_uuid
+ s3_endpoint = var.s3_endpoint
+ s3_secret_key = var.s3_secret_key
+ s3_access_key = var.s3_access_key
+ s3_bucket = var.mimir_bucket
+ s3_integrator_channel = local.channels.s3_integrator
+ s3_integrator_config = var.s3_integrator.config
+ s3_integrator_constraints = var.s3_integrator.constraints
+ s3_integrator_revision = local.revisions.s3_integrator
+ s3_integrator_storage_directives = var.s3_integrator.storage_directives
+ s3_integrator_units = var.s3_integrator.units
+ coordinator_config = { "max_global_exemplars_per_user" = "100000" }
coordinator_constraints = var.mimir_coordinator.constraints
- coordinator_revision = var.mimir_coordinator.revision
+ coordinator_revision = local.revisions.mimir_coordinator
coordinator_storage_directives = var.mimir_coordinator.storage_directives
coordinator_units = var.mimir_coordinator.units
backend_config = var.mimir_worker.backend_config
read_config = var.mimir_worker.read_config
write_config = var.mimir_worker.write_config
worker_constraints = var.mimir_worker.constraints
- worker_revision = var.mimir_worker.revision
+ worker_revision = local.revisions.mimir_worker
backend_worker_storage_directives = var.mimir_worker.backend_storage_directives
read_worker_storage_directives = var.mimir_worker.read_storage_directives
write_worker_storage_directives = var.mimir_worker.write_storage_directives
@@ -111,11 +103,11 @@ module "mimir" {
module "opentelemetry_collector" {
source = "git::https://github.com/canonical/opentelemetry-collector-k8s-operator//terraform"
app_name = var.opentelemetry_collector.app_name
- channel = var.channel
+ channel = local.channels.otelcol
config = var.opentelemetry_collector.config
constraints = var.opentelemetry_collector.constraints
model_uuid = var.model_uuid
- revision = var.opentelemetry_collector.revision
+ revision = local.revisions.otelcol
storage_directives = var.opentelemetry_collector.storage_directives
units = var.opentelemetry_collector.units
}
@@ -124,32 +116,33 @@ module "ssc" {
count = var.internal_tls ? 1 : 0
source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform"
app_name = var.ssc.app_name
- channel = var.ssc.channel
+ channel = local.channels.ssc
config = var.ssc.config
constraints = var.ssc.constraints
model_uuid = var.model_uuid
- revision = var.ssc.revision
+ revision = local.revisions.ssc
units = var.ssc.units
}
module "tempo" {
- source = "git::https://github.com/canonical/tempo-operators//terraform"
- anti_affinity = var.anti_affinity
- channel = var.channel
- model_uuid = var.model_uuid
- s3_endpoint = var.s3_endpoint
- s3_access_key = var.s3_access_key
- s3_secret_key = var.s3_secret_key
- s3_bucket = var.tempo_bucket
- s3_integrator_channel = var.s3_integrator.channel
+ source = "git::https://github.com/canonical/tempo-operators//terraform"
+ anti_affinity = var.anti_affinity
+ channel = local.channels.tempo
+ model_uuid = var.model_uuid
+ s3_endpoint = var.s3_endpoint
+ s3_access_key = var.s3_access_key
+ s3_secret_key = var.s3_secret_key
+ s3_bucket = var.tempo_bucket
+ # TODO: The same s3_integrator channel for all coordinated-workers?
+ s3_integrator_channel = local.channels.s3_integrator
s3_integrator_config = var.s3_integrator.config
s3_integrator_constraints = var.s3_integrator.constraints
- s3_integrator_revision = var.s3_integrator.revision
+ s3_integrator_revision = local.revisions.s3_integrator
s3_integrator_storage_directives = var.s3_integrator.storage_directives
s3_integrator_units = var.s3_integrator.units
coordinator_config = var.tempo_coordinator.config
coordinator_constraints = var.tempo_coordinator.constraints
- coordinator_revision = var.tempo_coordinator.revision
+ coordinator_revision = local.revisions.tempo_coordinator
coordinator_storage_directives = var.tempo_coordinator.storage_directives
coordinator_units = var.tempo_coordinator.units
querier_config = var.tempo_worker.querier_config
@@ -159,7 +152,7 @@ module "tempo" {
compactor_config = var.tempo_worker.compactor_config
metrics_generator_config = var.tempo_worker.metrics_generator_config
worker_constraints = var.tempo_worker.constraints
- worker_revision = var.tempo_worker.revision
+ worker_revision = local.revisions.tempo_worker
compactor_worker_storage_directives = var.tempo_worker.compactor_worker_storage_directives
distributor_worker_storage_directives = var.tempo_worker.distributor_worker_storage_directives
ingester_worker_storage_directives = var.tempo_worker.ingester_worker_storage_directives
@@ -177,11 +170,11 @@ module "tempo" {
module "traefik" {
source = "git::https://github.com/canonical/traefik-k8s-operator//terraform"
app_name = var.traefik.app_name
- channel = var.traefik.channel
+ channel = local.channels.traefik
config = var.cloud == "aws" ? { "loadbalancer_annotations" = "service.beta.kubernetes.io/aws-load-balancer-scheme=internet-facing" } : var.traefik.config
constraints = var.traefik.constraints
model_uuid = var.model_uuid
- revision = var.traefik.revision
+ revision = local.revisions.traefik
storage_directives = var.traefik.storage_directives
units = var.traefik.units
}
diff --git a/terraform/cos/locals.tf b/terraform/cos/locals.tf
new file mode 100644
index 00000000..bae1f726
--- /dev/null
+++ b/terraform/cos/locals.tf
@@ -0,0 +1,43 @@
+locals {
+ clouds = ["aws", "self-managed"] # list of k8s clouds where this COS module can be deployed.
+ tls_termination = var.external_certificates_offer_url != null ? true : false
+ tracks = {
+ alertmanager = "0.31"
+ catalogue = "3.0"
+ grafana = "12.4"
+ loki = "3.7"
+ mimir = "3.0"
+ otelcol = "0.130"
+ s3_integrator = "2"
+ ssc = "latest"
+ tempo = "2.10"
+ traefik = "latest"
+ }
+ channels = {
+ alertmanager = "${local.tracks.alertmanager}/${var.risk}"
+ catalogue = "${local.tracks.catalogue}/${var.risk}"
+ grafana = "${local.tracks.grafana}/${var.risk}"
+ loki = "${local.tracks.loki}/${var.risk}"
+ mimir = "${local.tracks.mimir}/${var.risk}"
+ otelcol = "${local.tracks.otelcol}/${var.risk}"
+ s3_integrator = "${local.tracks.s3_integrator}/${var.risk}"
+ ssc = "${local.tracks.ssc}/${var.risk}"
+ tempo = "${local.tracks.tempo}/${var.risk}"
+ traefik = "${local.tracks.traefik}/${var.risk}"
+ }
+ revisions = {
+ 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
+ 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
+ mimir_worker = var.mimir_worker.revision != null ? var.mimir_worker.revision : data.juju_charm.mimir_worker_info.revision
+ otelcol = var.opentelemetry_collector.revision != null ? var.opentelemetry_collector.revision : data.juju_charm.otelcol_info.revision
+ s3_integrator = var.s3_integrator.revision != null ? var.s3_integrator.revision : data.juju_charm.s3_integrator_info.revision
+ ssc = var.ssc.revision != null ? var.ssc.revision : data.juju_charm.ssc_info.revision
+ tempo_coordinator = var.tempo_coordinator.revision != null ? var.tempo_coordinator.revision : data.juju_charm.tempo_coordinator_info.revision
+ tempo_worker = var.tempo_worker.revision != null ? var.tempo_worker.revision : data.juju_charm.tempo_worker_info.revision
+ traefik = var.traefik.revision != null ? var.traefik.revision : data.juju_charm.traefik_info.revision
+ }
+}
diff --git a/terraform/cos/tests/revision_pin.tftest.hcl b/terraform/cos/tests/revision_pin.tftest.hcl
new file mode 100644
index 00000000..c9536cf5
--- /dev/null
+++ b/terraform/cos/tests/revision_pin.tftest.hcl
@@ -0,0 +1,166 @@
+mock_provider "juju" {}
+
+variables {
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ s3_endpoint = "foo"
+ s3_access_key = "foo"
+ s3_secret_key = "foo"
+}
+
+# --- User revision pin is respected and not overridden by juju_charm datasource ---
+
+run "user_revision_pin_is_respected" {
+ command = plan
+
+ variables {
+ alertmanager = { revision = 1 }
+ catalogue = { revision = 2 }
+ grafana = { revision = 3 }
+ loki_coordinator = { revision = 4 }
+ loki_worker = { revision = 5 }
+ mimir_coordinator = { revision = 6 }
+ mimir_worker = { revision = 7 }
+ opentelemetry_collector = { revision = 8 }
+ ssc = { revision = 9 }
+ s3_integrator = { revision = 10 }
+ tempo_coordinator = { revision = 11 }
+ tempo_worker = { revision = 12 }
+ traefik = { revision = 13 }
+ }
+
+ assert {
+ condition = local.revisions.alertmanager == 1
+ error_message = "Expected alertmanager revision 1, got ${local.revisions.alertmanager}"
+ }
+
+ assert {
+ condition = local.revisions.catalogue == 2
+ error_message = "Expected catalogue revision 2, got ${local.revisions.catalogue}"
+ }
+
+ assert {
+ condition = local.revisions.grafana == 3
+ error_message = "Expected grafana revision 3, got ${local.revisions.grafana}"
+ }
+
+ assert {
+ condition = local.revisions.loki_coordinator == 4
+ error_message = "Expected loki_coordinator revision 4, got ${local.revisions.loki_coordinator}"
+ }
+
+ assert {
+ condition = local.revisions.loki_worker == 5
+ error_message = "Expected loki_worker revision 5, got ${local.revisions.loki_worker}"
+ }
+
+ assert {
+ condition = local.revisions.mimir_coordinator == 6
+ error_message = "Expected mimir_coordinator revision 6, got ${local.revisions.mimir_coordinator}"
+ }
+
+ assert {
+ condition = local.revisions.mimir_worker == 7
+ error_message = "Expected mimir_worker revision 7, got ${local.revisions.mimir_worker}"
+ }
+
+ assert {
+ condition = local.revisions.otelcol == 8
+ error_message = "Expected otelcol revision 8, got ${local.revisions.otelcol}"
+ }
+
+ assert {
+ condition = local.revisions.ssc == 9
+ error_message = "Expected ssc revision 9, got ${local.revisions.ssc}"
+ }
+
+ assert {
+ condition = local.revisions.s3_integrator == 10
+ error_message = "Expected s3_integrator revision 10, got ${local.revisions.s3_integrator}"
+ }
+
+ assert {
+ condition = local.revisions.tempo_coordinator == 11
+ error_message = "Expected tempo_coordinator revision 11, got ${local.revisions.tempo_coordinator}"
+ }
+
+ assert {
+ condition = local.revisions.tempo_worker == 12
+ error_message = "Expected tempo_worker revision 12, got ${local.revisions.tempo_worker}"
+ }
+
+ assert {
+ condition = local.revisions.traefik == 13
+ error_message = "Expected traefik revision 13, got ${local.revisions.traefik}"
+ }
+}
+
+# --- Without a revision pin, the juju_charm datasource determines the revision ---
+
+run "no_pin_uses_datasource" {
+ command = plan
+
+ assert {
+ condition = local.revisions.alertmanager == data.juju_charm.alertmanager_info.revision
+ error_message = "alertmanager revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.catalogue == data.juju_charm.catalogue_info.revision
+ error_message = "catalogue revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.grafana == data.juju_charm.grafana_info.revision
+ error_message = "grafana revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.loki_coordinator == data.juju_charm.loki_coordinator_info.revision
+ error_message = "loki_coordinator revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.loki_worker == data.juju_charm.loki_worker_info.revision
+ error_message = "loki_worker revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.mimir_coordinator == data.juju_charm.mimir_coordinator_info.revision
+ error_message = "mimir_coordinator revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.mimir_worker == data.juju_charm.mimir_worker_info.revision
+ error_message = "mimir_worker revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.otelcol == data.juju_charm.otelcol_info.revision
+ error_message = "otelcol revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.ssc == data.juju_charm.ssc_info.revision
+ error_message = "ssc revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.s3_integrator == data.juju_charm.s3_integrator_info.revision
+ error_message = "s3_integrator revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.tempo_coordinator == data.juju_charm.tempo_coordinator_info.revision
+ error_message = "tempo_coordinator revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.tempo_worker == data.juju_charm.tempo_worker_info.revision
+ error_message = "tempo_worker revision should come from datasource when no pin is set"
+ }
+
+ assert {
+ condition = local.revisions.traefik == data.juju_charm.traefik_info.revision
+ error_message = "traefik revision should come from datasource when no pin is set"
+ }
+}
diff --git a/terraform/cos/upgrades.tf b/terraform/cos/upgrades.tf
new file mode 100644
index 00000000..429bcaa4
--- /dev/null
+++ b/terraform/cos/upgrades.tf
@@ -0,0 +1,79 @@
+# -------------- # CharmHub API -------------- #
+
+data "juju_charm" "alertmanager_info" {
+ charm = "alertmanager-k8s"
+ channel = local.channels.alertmanager
+ base = var.base
+}
+
+data "juju_charm" "catalogue_info" {
+ charm = "catalogue-k8s"
+ channel = local.channels.catalogue
+ base = var.base
+}
+
+data "juju_charm" "grafana_info" {
+ charm = "grafana-k8s"
+ channel = local.channels.grafana
+ base = var.base
+}
+
+data "juju_charm" "loki_coordinator_info" {
+ charm = "loki-coordinator-k8s"
+ channel = local.channels.loki
+ base = var.base
+}
+
+data "juju_charm" "loki_worker_info" {
+ charm = "loki-worker-k8s"
+ channel = local.channels.loki
+ base = var.base
+}
+
+data "juju_charm" "mimir_coordinator_info" {
+ charm = "mimir-coordinator-k8s"
+ channel = local.channels.mimir
+ base = var.base
+}
+
+data "juju_charm" "mimir_worker_info" {
+ charm = "mimir-worker-k8s"
+ channel = local.channels.mimir
+ base = var.base
+}
+
+data "juju_charm" "otelcol_info" {
+ charm = "opentelemetry-collector-k8s"
+ channel = local.channels.otelcol
+ base = var.base
+}
+
+data "juju_charm" "tempo_coordinator_info" {
+ charm = "tempo-coordinator-k8s"
+ channel = local.channels.tempo
+ base = var.base
+}
+
+data "juju_charm" "tempo_worker_info" {
+ charm = "tempo-worker-k8s"
+ channel = local.channels.tempo
+ base = var.base
+}
+
+data "juju_charm" "ssc_info" {
+ charm = "self-signed-certificates"
+ channel = local.channels.ssc
+ base = var.base
+}
+
+data "juju_charm" "s3_integrator_info" {
+ charm = "s3-integrator-k8s"
+ channel = local.channels.s3_integrator
+ base = var.base
+}
+
+data "juju_charm" "traefik_info" {
+ charm = "traefik-k8s"
+ channel = local.channels.traefik
+ base = var.base
+}
diff --git a/terraform/cos/variables.tf b/terraform/cos/variables.tf
index 2c45184d..3a337536 100644
--- a/terraform/cos/variables.tf
+++ b/terraform/cos/variables.tf
@@ -5,15 +5,16 @@
# causes the operation to fail due to https://github.com/juju/terraform-provider-juju/issues/344
# Therefore, we set a default value of "arch=amd64" for all applications.
-locals {
- clouds = ["aws", "self-managed"] # list of k8s clouds where this COS module can be deployed.
- tls_termination = var.external_certificates_offer_url != null ? true : false
+variable "risk" {
+ description = "Risk level that the applications are (unless overwritten by individual channels) deployed from"
+ type = string
+ default = "edge"
}
-variable "channel" {
- description = "Channel that the applications are (unless overwritten by individual channels) deployed from"
+variable "base" {
+ description = "The operating system on which to deploy. E.g. ubuntu@22.04. Changing this value for machine charms will trigger a replace by terraform. Check Charmhub for per-charm base support."
+ default = "ubuntu@24.04"
type = string
- default = "dev/edge"
}
variable "model_uuid" {
@@ -234,7 +235,6 @@ variable "opentelemetry_collector" {
description = "Application configuration for OpenTelemetry Collector. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application"
}
-
variable "ssc" {
type = object({
app_name = optional(string, "ca")