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