diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml index d52feee..2f45139 100644 --- a/.github/workflows/terraform.yml +++ b/.github/workflows/terraform.yml @@ -43,6 +43,18 @@ jobs: sudo snap install just --classic - name: Validate the Terraform modules run: just validate-terraform + test-unit: + name: Terraform unit tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install dependencies + run: | + sudo snap install terraform --classic + sudo snap install just --classic + - name: Unit test the Terraform modules + run: just unit test-integration-cos-lite: name: COS Lite Terraform integration uses: canonical/observability-stack/.github/workflows/_integration.yml@main diff --git a/justfile b/justfile index 066c679..463de79 100644 --- a/justfile +++ b/justfile @@ -20,6 +20,10 @@ lint: lint-workflows lint-terraform lint-terraform-docs [group("Format")] fmt: format-terraform format-terraform-docs +# Run unit tests +[group("Unit")] +unit: (unit-test "cos") (unit-test "cos-lite") + # Lint the Github workflows [group("Lint")] lint-workflows: @@ -35,7 +39,7 @@ lint-terraform: # Lint the Terraform documentation [group("Lint")] lint-terraform-docs: - terraform-docs --config .tfdocs-config.yml . + terraform-docs --config .tfdocs-config.yml --output-check . # Format the Terraform modules [group("Format")] @@ -50,12 +54,21 @@ format-terraform-docs: terraform-docs --config .tfdocs-config.yml . # Validate the Terraform modules +[group("Static")] [working-directory("./terraform")] validate-terraform: if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi set -e; for repo in */; do (cd "$repo" && echo "Processing ${repo%/}..." && $terraform init -upgrade && $terraform validate) || exit 1; done +# Run a unit test +[group("Unit")] +[working-directory("./terraform")] +unit-test module: + if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi + $terraform -chdir={{module}} init -upgrade && $terraform -chdir={{module}} test + # Run integration tests +[group("Integration")] [working-directory("./tests/integration")] integration *args='': uv run ${uv_flags} pytest -vv --capture=no --exitfirst "${args}" diff --git a/terraform/cos-lite/README.md b/terraform/cos-lite/README.md index 5fec615..4cafb3a 100644 --- a/terraform/cos-lite/README.md +++ b/terraform/cos-lite/README.md @@ -13,11 +13,11 @@ This is a Terraform module facilitating the deployment of the COS Lite solution, | Name | Source | Version | |------|--------|---------| -| [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 | -| [loki](#module\_loki) | git::https://github.com/canonical/loki-k8s-operator//terraform | n/a | -| [prometheus](#module\_prometheus) | git::https://github.com/canonical/prometheus-k8s-operator//terraform | n/a | +| [alertmanager](#module\_alertmanager) | git::https://github.com/canonical/alertmanager-k8s-operator//terraform | track/2 | +| [catalogue](#module\_catalogue) | git::https://github.com/canonical/catalogue-k8s-operator//terraform | track/2 | +| [grafana](#module\_grafana) | git::https://github.com/canonical/grafana-k8s-operator//terraform | track/2 | +| [loki](#module\_loki) | git::https://github.com/canonical/loki-k8s-operator//terraform | track/2 | +| [prometheus](#module\_prometheus) | git::https://github.com/canonical/prometheus-k8s-operator//terraform | track/2 | | [ssc](#module\_ssc) | git::https://github.com/canonical/self-signed-certificates-operator//terraform | n/a | | [traefik](#module\_traefik) | git::https://github.com/canonical/traefik-k8s-operator//terraform | n/a | @@ -27,13 +27,13 @@ This is a Terraform module facilitating the deployment of the COS Lite solution, |------|-------------|------|---------|:--------:| | [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 |
| [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 external\_channels) deployed from | `string` | n/a | yes |
+| [channel](#input\_channel) | Channel that the applications are (unless overwritten by external\_channels) deployed from | `string` | `"2/stable"` | 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 |
| [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](#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 |
+| [model\_uuid](#input\_model\_uuid) | Reference to an existing model resource or data source for the model to deploy to | `string` | `"foo"` | no |
| [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 |
| [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/tests/channel_validation.tftest.hcl b/terraform/cos-lite/tests/channel_validation.tftest.hcl
new file mode 100644
index 0000000..101cd04
--- /dev/null
+++ b/terraform/cos-lite/tests/channel_validation.tftest.hcl
@@ -0,0 +1,85 @@
+mock_provider "juju" {}
+
+# ---Happy path---
+
+run "valid_channel_track" {
+ command = plan
+
+ variables {
+ channel = "2/stable"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ }
+}
+
+run "valid_channel_stable" {
+ command = plan
+
+ variables {
+ channel = "2/candidate"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ }
+}
+
+run "valid_channel_candidate" {
+ command = plan
+
+ variables {
+ channel = "2/beta"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ }
+}
+
+run "valid_channel_beta" {
+ command = plan
+
+ variables {
+ channel = "2/edge"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ }
+}
+
+# ---Failure path---
+
+run "invalid_channel_track" {
+ command = plan
+
+ variables {
+ channel = "1/stable"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ }
+
+ expect_failures = [var.channel]
+}
+
+run "invalid_channel_track_numeric" {
+ command = plan
+
+ variables {
+ channel = "123/risk"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ }
+
+ expect_failures = [var.channel]
+}
+
+run "invalid_channel_track_string" {
+ command = plan
+
+ variables {
+ channel = "foo/risk"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ }
+
+ expect_failures = [var.channel]
+}
+
+run "invalid_channel_track_dev" {
+ command = plan
+
+ variables {
+ channel = "dev/risk"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ }
+
+ expect_failures = [var.channel]
+}
diff --git a/terraform/cos-lite/variables.tf b/terraform/cos-lite/variables.tf
index e727469..19c5e47 100644
--- a/terraform/cos-lite/variables.tf
+++ b/terraform/cos-lite/variables.tf
@@ -14,6 +14,11 @@ variable "channel" {
description = "Channel that the applications are (unless overwritten by external_channels) deployed from"
type = string
default = "2/stable"
+
+ validation {
+ condition = startswith(var.channel, "2/")
+ error_message = "The track of the channel must be '2/'. e.g. '2/stable'."
+ }
}
variable "model_uuid" {
diff --git a/terraform/cos/README.md b/terraform/cos/README.md
index 4a95100..a2ac0c2 100644
--- a/terraform/cos/README.md
+++ b/terraform/cos/README.md
@@ -16,14 +16,14 @@ This is a Terraform module facilitating the deployment of the COS solution, usin
| Name | Source | Version |
|------|--------|---------|
-| [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 |
-| [loki](#module\_loki) | git::https://github.com/canonical/observability-stack//terraform/loki | n/a |
-| [mimir](#module\_mimir) | git::https://github.com/canonical/observability-stack//terraform/mimir | n/a |
-| [opentelemetry\_collector](#module\_opentelemetry\_collector) | git::https://github.com/canonical/opentelemetry-collector-k8s-operator//terraform | n/a |
+| [alertmanager](#module\_alertmanager) | git::https://github.com/canonical/alertmanager-k8s-operator//terraform | track/2 |
+| [catalogue](#module\_catalogue) | git::https://github.com/canonical/catalogue-k8s-operator//terraform | track/2 |
+| [grafana](#module\_grafana) | git::https://github.com/canonical/grafana-k8s-operator//terraform | track/2 |
+| [loki](#module\_loki) | git::https://github.com/canonical/observability-stack//terraform/loki | track/2 |
+| [mimir](#module\_mimir) | git::https://github.com/canonical/observability-stack//terraform/mimir | track/2 |
+| [opentelemetry\_collector](#module\_opentelemetry\_collector) | git::https://github.com/canonical/opentelemetry-collector-k8s-operator//terraform | track/2 |
| [ssc](#module\_ssc) | git::https://github.com/canonical/self-signed-certificates-operator//terraform | n/a |
-| [tempo](#module\_tempo) | git::https://github.com/canonical/tempo-operators//terraform | n/a |
+| [tempo](#module\_tempo) | git::https://github.com/canonical/tempo-operators//terraform | track/2 |
| [traefik](#module\_traefik) | git::https://github.com/canonical/traefik-k8s-operator//terraform | n/a |
## Inputs
@@ -33,7 +33,7 @@ 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 |
| [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 external\_channels) deployed from | `string` | n/a | yes |
+| [channel](#input\_channel) | Channel that the applications are (unless overwritten by external\_channels) deployed from | `string` | `"2/stable"` | 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 |
diff --git a/terraform/cos/tests/channel_validation.tftest.hcl b/terraform/cos/tests/channel_validation.tftest.hcl
new file mode 100644
index 0000000..cee0b7c
--- /dev/null
+++ b/terraform/cos/tests/channel_validation.tftest.hcl
@@ -0,0 +1,109 @@
+mock_provider "juju" {}
+
+# ---Happy path---
+
+run "valid_channel_track" {
+ command = plan
+
+ variables {
+ channel = "2/stable"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ s3_endpoint = "foo"
+ s3_access_key = "foo"
+ s3_secret_key = "foo"
+ }
+}
+
+run "valid_channel_stable" {
+ command = plan
+
+ variables {
+ channel = "2/candidate"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ s3_endpoint = "foo"
+ s3_access_key = "foo"
+ s3_secret_key = "foo"
+ }
+}
+
+run "valid_channel_candidate" {
+ command = plan
+
+ variables {
+ channel = "2/beta"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ s3_endpoint = "foo"
+ s3_access_key = "foo"
+ s3_secret_key = "foo"
+ }
+}
+
+run "valid_channel_beta" {
+ command = plan
+
+ variables {
+ channel = "2/edge"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ s3_endpoint = "foo"
+ s3_access_key = "foo"
+ s3_secret_key = "foo"
+ }
+}
+
+# ---Failure path---
+
+run "invalid_channel_track" {
+ command = plan
+
+ variables {
+ channel = "1/stable"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ s3_endpoint = "foo"
+ s3_access_key = "foo"
+ s3_secret_key = "foo"
+ }
+
+ expect_failures = [var.channel]
+}
+
+run "invalid_channel_track_numeric" {
+ command = plan
+
+ variables {
+ channel = "123/risk"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ s3_endpoint = "foo"
+ s3_access_key = "foo"
+ s3_secret_key = "foo"
+ }
+
+ expect_failures = [var.channel]
+}
+
+run "invalid_channel_track_string" {
+ command = plan
+
+ variables {
+ channel = "foo/risk"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ s3_endpoint = "foo"
+ s3_access_key = "foo"
+ s3_secret_key = "foo"
+ }
+
+ expect_failures = [var.channel]
+}
+
+run "invalid_channel_track_dev" {
+ command = plan
+
+ variables {
+ channel = "dev/risk"
+ model_uuid = "00000000-0000-0000-0000-000000000000"
+ s3_endpoint = "foo"
+ s3_access_key = "foo"
+ s3_secret_key = "foo"
+ }
+
+ expect_failures = [var.channel]
+}
diff --git a/terraform/cos/variables.tf b/terraform/cos/variables.tf
index 2ab7c6e..347d83b 100644
--- a/terraform/cos/variables.tf
+++ b/terraform/cos/variables.tf
@@ -14,6 +14,11 @@ variable "channel" {
description = "Channel that the applications are (unless overwritten by external_channels) deployed from"
type = string
default = "2/stable"
+
+ validation {
+ condition = split("/", var.channel)[0] == split("/", "2/stable")[0]
+ error_message = "The track (the part before the '/') of the channel must match the default."
+ }
}
variable "model_uuid" {
diff --git a/terraform/loki/README.md b/terraform/loki/README.md
index c3fa27c..a739bd6 100644
--- a/terraform/loki/README.md
+++ b/terraform/loki/README.md
@@ -16,10 +16,10 @@ This is a Terraform module facilitating the deployment of Loki solution, using t
| Name | Source | Version |
|------|--------|---------|
-| [loki\_backend](#module\_loki\_backend) | git::https://github.com/canonical/loki-worker-k8s-operator//terraform | n/a |
-| [loki\_coordinator](#module\_loki\_coordinator) | git::https://github.com/canonical/loki-coordinator-k8s-operator//terraform | n/a |
-| [loki\_read](#module\_loki\_read) | git::https://github.com/canonical/loki-worker-k8s-operator//terraform | n/a |
-| [loki\_write](#module\_loki\_write) | git::https://github.com/canonical/loki-worker-k8s-operator//terraform | n/a |
+| [loki\_backend](#module\_loki\_backend) | git::https://github.com/canonical/loki-worker-k8s-operator//terraform | track/2 |
+| [loki\_coordinator](#module\_loki\_coordinator) | git::https://github.com/canonical/loki-coordinator-k8s-operator//terraform | track/2 |
+| [loki\_read](#module\_loki\_read) | git::https://github.com/canonical/loki-worker-k8s-operator//terraform | track/2 |
+| [loki\_write](#module\_loki\_write) | git::https://github.com/canonical/loki-worker-k8s-operator//terraform | track/2 |
## Inputs
diff --git a/terraform/mimir/README.md b/terraform/mimir/README.md
index db9b069..3de85a9 100644
--- a/terraform/mimir/README.md
+++ b/terraform/mimir/README.md
@@ -16,10 +16,10 @@ This is a Terraform module facilitating the deployment of Mimir solution, using
| Name | Source | Version |
|------|--------|---------|
-| [mimir\_backend](#module\_mimir\_backend) | git::https://github.com/canonical/mimir-worker-k8s-operator//terraform | n/a |
-| [mimir\_coordinator](#module\_mimir\_coordinator) | git::https://github.com/canonical/mimir-coordinator-k8s-operator//terraform | n/a |
-| [mimir\_read](#module\_mimir\_read) | git::https://github.com/canonical/mimir-worker-k8s-operator//terraform | n/a |
-| [mimir\_write](#module\_mimir\_write) | git::https://github.com/canonical/mimir-worker-k8s-operator//terraform | n/a |
+| [mimir\_backend](#module\_mimir\_backend) | git::https://github.com/canonical/mimir-worker-k8s-operator//terraform | track/2 |
+| [mimir\_coordinator](#module\_mimir\_coordinator) | git::https://github.com/canonical/mimir-coordinator-k8s-operator//terraform | track/2 |
+| [mimir\_read](#module\_mimir\_read) | git::https://github.com/canonical/mimir-worker-k8s-operator//terraform | track/2 |
+| [mimir\_write](#module\_mimir\_write) | git::https://github.com/canonical/mimir-worker-k8s-operator//terraform | track/2 |
## Inputs