Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions coordinator/terraform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@
This is a Terraform module facilitating the deployment of mimir-coordinator-k8s charm, using the [Terraform juju provider](https://github.com/juju/terraform-provider-juju/). For more information, refer to the provider [documentation](https://registry.terraform.io/providers/juju/juju/latest/docs).

<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.5 |
| <a name="requirement_juju"></a> [juju](#requirement\_juju) | >= 1.0 |

## Providers

| Name | Version |
Expand Down
124 changes: 96 additions & 28 deletions terraform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@ This is a Terraform module facilitating the deployment of Mimir solution, using
> `s3-integrator` itself doesn't act as an S3 object storage system. For the HA solution to be functional, `s3-integrator` needs to point to an S3-like storage. See [this guide](https://discourse.charmhub.io/t/cos-lite-docs-set-up-minio/15211) to learn how to connect to an S3-like storage for traces.

<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.5 |
| <a name="requirement_juju"></a> [juju](#requirement\_juju) | >= 1.0 |

## Providers

| Name | Version |
Expand All @@ -23,20 +16,14 @@ This is a Terraform module facilitating the deployment of Mimir solution, using

| Name | Source | Version |
|------|--------|---------|
| <a name="module_mimir_backend"></a> [mimir\_backend](#module\_mimir\_backend) | ../worker/terraform | n/a |
| <a name="module_mimir_coordinator"></a> [mimir\_coordinator](#module\_mimir\_coordinator) | ../coordinator/terraform | n/a |
| <a name="module_mimir_read"></a> [mimir\_read](#module\_mimir\_read) | ../worker/terraform | n/a |
| <a name="module_mimir_write"></a> [mimir\_write](#module\_mimir\_write) | ../worker/terraform | n/a |
| <a name="module_mimir_worker"></a> [mimir\_worker](#module\_mimir\_worker) | ../worker/terraform | n/a |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_anti_affinity"></a> [anti\_affinity](#input\_anti\_affinity) | Enable anti-affinity constraints. | `bool` | `true` | no |
| <a name="input_backend_config"></a> [backend\_config](#input\_backend\_config) | Map of the backend worker configuration options | `map(string)` | `{}` | no |
| <a name="input_backend_name"></a> [backend\_name](#input\_backend\_name) | Name of the Mimir backend (meta role) app | `string` | `"mimir-backend"` | no |
| <a name="input_backend_units"></a> [backend\_units](#input\_backend\_units) | Number of Mimir worker units with the backend meta role | `number` | `1` | no |
| <a name="input_backend_worker_storage_directives"></a> [backend\_worker\_storage\_directives](#input\_backend\_worker\_storage\_directives) | Map of storage used by the backend worker application, which defaults to 1 GB, allocated by Juju | `map(string)` | `{}` | no |
| <a name="input_channel"></a> [channel](#input\_channel) | Channel that the applications are deployed from | `string` | n/a | yes |
| <a name="input_coordinator_config"></a> [coordinator\_config](#input\_coordinator\_config) | Map of the coordinator configuration options | `map(string)` | `{}` | no |
| <a name="input_coordinator_constraints"></a> [coordinator\_constraints](#input\_coordinator\_constraints) | String listing constraints for the coordinator application | `string` | `"arch=amd64"` | no |
Expand All @@ -45,28 +32,20 @@ This is a Terraform module facilitating the deployment of Mimir solution, using
| <a name="input_coordinator_storage_directives"></a> [coordinator\_storage\_directives](#input\_coordinator\_storage\_directives) | Map of storage used by the coordinator application, which defaults to 1 GB, allocated by Juju | `map(string)` | `{}` | no |
| <a name="input_coordinator_units"></a> [coordinator\_units](#input\_coordinator\_units) | Number of Mimir coordinator units | `number` | `1` | no |
| <a name="input_model_uuid"></a> [model\_uuid](#input\_model\_uuid) | Reference to an existing model resource or data source for the model to deploy to | `string` | n/a | yes |
| <a name="input_read_config"></a> [read\_config](#input\_read\_config) | Map of the read worker configuration options | `map(string)` | `{}` | no |
| <a name="input_read_name"></a> [read\_name](#input\_read\_name) | Name of the Mimir read (meta role) app | `string` | `"mimir-read"` | no |
| <a name="input_read_units"></a> [read\_units](#input\_read\_units) | Number of Mimir worker units with the read meta role | `number` | `1` | no |
| <a name="input_read_worker_storage_directives"></a> [read\_worker\_storage\_directives](#input\_read\_worker\_storage\_directives) | Map of storage used by the read worker application, which defaults to 1 GB, allocated by Juju | `map(string)` | `{}` | no |
| <a name="input_s3_access_key"></a> [s3\_access\_key](#input\_s3\_access\_key) | S3 access-key credential | `string` | n/a | yes |
| <a name="input_s3_access_key"></a> [s3\_access\_key](#input\_s3\_access\_key) | S3 access-key credential. Set to null (along with s3\_endpoint and s3\_secret\_key) to skip deploying the S3 integrator. | `string` | `null` | no |
| <a name="input_s3_bucket"></a> [s3\_bucket](#input\_s3\_bucket) | Bucket name | `string` | `"mimir"` | no |
| <a name="input_s3_endpoint"></a> [s3\_endpoint](#input\_s3\_endpoint) | S3 endpoint | `string` | n/a | yes |
| <a name="input_s3_endpoint"></a> [s3\_endpoint](#input\_s3\_endpoint) | S3 endpoint. When null, the S3 integrator is not deployed and the caller must handle storage integration externally. | `string` | `null` | no |
| <a name="input_s3_integrator_channel"></a> [s3\_integrator\_channel](#input\_s3\_integrator\_channel) | Channel that the s3-integrator application is deployed from | `string` | `"2/stable"` | no |
| <a name="input_s3_integrator_config"></a> [s3\_integrator\_config](#input\_s3\_integrator\_config) | Map of the s3-integrator configuration options | `map(string)` | `{}` | no |
| <a name="input_s3_integrator_constraints"></a> [s3\_integrator\_constraints](#input\_s3\_integrator\_constraints) | String listing constraints for the s3-integrator application | `string` | `"arch=amd64"` | no |
| <a name="input_s3_integrator_name"></a> [s3\_integrator\_name](#input\_s3\_integrator\_name) | Name of the s3-integrator app | `string` | `"mimir-s3-integrator"` | no |
| <a name="input_s3_integrator_revision"></a> [s3\_integrator\_revision](#input\_s3\_integrator\_revision) | Revision number of the s3-integrator application | `number` | `null` | no |
| <a name="input_s3_integrator_storage_directives"></a> [s3\_integrator\_storage\_directives](#input\_s3\_integrator\_storage\_directives) | Map of storage used by the s3-integrator application, which defaults to 1 GB, allocated by Juju | `map(string)` | `{}` | no |
| <a name="input_s3_integrator_units"></a> [s3\_integrator\_units](#input\_s3\_integrator\_units) | Number of S3 integrator units | `number` | `1` | no |
| <a name="input_s3_secret_key"></a> [s3\_secret\_key](#input\_s3\_secret\_key) | S3 secret-key credential | `string` | n/a | yes |
| <a name="input_worker_constraints"></a> [worker\_constraints](#input\_worker\_constraints) | String listing constraints for the worker application | `string` | `"arch=amd64"` | no |
| <a name="input_s3_secret_key"></a> [s3\_secret\_key](#input\_s3\_secret\_key) | S3 secret-key credential. Set to null (along with s3\_endpoint and s3\_access\_key) to skip deploying the S3 integrator. | `string` | `null` | no |
| <a name="input_worker_resources"></a> [worker\_resources](#input\_worker\_resources) | The worker application's resources i.e., a resource revision number from CharmHub or a custom OCI image resource | `map(string)` | `{}` | no |
| <a name="input_worker_revision"></a> [worker\_revision](#input\_worker\_revision) | Revision number of the worker application | `number` | `null` | no |
| <a name="input_write_config"></a> [write\_config](#input\_write\_config) | Map of the write worker configuration options | `map(string)` | `{}` | no |
| <a name="input_write_name"></a> [write\_name](#input\_write\_name) | Name of the Mimir write (meta role) app | `string` | `"mimir-write"` | no |
| <a name="input_write_units"></a> [write\_units](#input\_write\_units) | Number of Mimir worker units with the write meta role | `number` | `1` | no |
| <a name="input_write_worker_storage_directives"></a> [write\_worker\_storage\_directives](#input\_write\_worker\_storage\_directives) | Map of storage used by the write worker application, which defaults to 1 GB, allocated by Juju | `map(string)` | `{}` | no |
| <a name="input_workers"></a> [workers](#input\_workers) | Map of worker roles to deploy. Keys must be one of: all, backend, read, write. When 'all' is used, a single worker with role-all is created. | <pre>map(object({<br/> units = optional(number, 1)<br/> config = optional(map(string), {})<br/> constraints = optional(string, "arch=amd64")<br/> storage_directives = optional(map(string), {})<br/> app_name = optional(string)<br/> }))</pre> | <pre>{<br/> "backend": {},<br/> "read": {},<br/> "write": {}<br/>}</pre> | no |

## Outputs

Expand All @@ -79,7 +58,96 @@ This is a Terraform module facilitating the deployment of Mimir solution, using

## Usage

### Microservice deployment
### Microservices deployment (default)

By default, this module deploys three separate workers — `backend`, `read`, and `write` — each with 1 unit:

```hcl
module "mimir" {
source = "git::https://github.com/canonical/mimir-operators//terraform"
model_uuid = juju_model.cos.uuid
channel = "dev/edge"

s3_endpoint = "https://s3.example.com"
s3_access_key = "my-access-key"
s3_secret_key = "my-secret-key"
}
```

To scale individual roles:

```hcl
module "mimir" {
source = "git::https://github.com/canonical/mimir-operators//terraform"
model_uuid = juju_model.cos.uuid
channel = "dev/edge"

workers = {
backend = { units = 3, storage_directives = { "data" = "50G" } }
read = { units = 2 }
write = { units = 2 }
}

s3_endpoint = "https://s3.example.com"
s3_access_key = "my-access-key"
s3_secret_key = "my-secret-key"
}
```

By default, this Terraform module will deploy each Mimir worker with `1` unit. To configure the module to run `x` units of any worker role, you can run `terraform apply -var="<ROLE>_units=<x>"`.
See [Mimir worker roles](https://discourse.charmhub.io/t/mimir-worker-roles/15484) for the recommended scale for each role.

### Monolithic deployment

To deploy a single worker with all roles combined:

```hcl
module "mimir" {
source = "git::https://github.com/canonical/mimir-operators//terraform"
model_uuid = juju_model.cos.uuid
channel = "dev/edge"

workers = {
all = { units = 3 }
}

s3_endpoint = "https://s3.example.com"
s3_access_key = "my-access-key"
s3_secret_key = "my-secret-key"
}
```

> [!NOTE]
> When using `all`, no other worker roles may be specified.

### External storage backend (no S3 integrator)

When using an external storage backend (e.g. SeaweedFS), omit the `s3_*` variables. The module will not deploy the S3 integrator, and you are responsible for integrating your storage with the coordinator's `s3` endpoint:

@mmkay mmkay Jun 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it might be tricky which storage backend is external and which is internal. I think the difference is mostly in whether the storage backend supports the s3 interface or not.


```hcl
module "mimir" {
source = "git::https://github.com/canonical/mimir-operators//terraform"
model_uuid = juju_model.cos.uuid
channel = "dev/edge"

workers = {
backend = { units = 2 }
read = { units = 2 }
write = { units = 2 }
}
}

# Wire external storage to the coordinator
resource "juju_integration" "seaweedfs_mimir" {
model_uuid = juju_model.cos.uuid

application {
name = module.seaweedfs.app_name
endpoint = module.seaweedfs.provides.s3
}

application {
name = module.mimir.app_names.mimir_coordinator
endpoint = module.mimir.requires.s3
}
}
```
64 changes: 64 additions & 0 deletions terraform/tests/s3_optional.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
mock_provider "juju" {}

variables {
model_uuid = "00000000-0000-0000-0000-000000000000"
channel = "dev/edge"
}

# --- default: S3 disabled (no integrator deployed) ---

run "s3_disabled_by_default" {
command = plan

assert {
condition = length(juju_application.s3_integrator) == 0
error_message = "Expected no s3-integrator when s3_endpoint is null"
}

assert {
condition = length(juju_secret.mimir_s3_credentials_secret) == 0
error_message = "Expected no s3 credentials secret when s3_endpoint is null"
}

assert {
condition = length(juju_access_secret.mimir_s3_secret_access) == 0
error_message = "Expected no s3 access secret when s3_endpoint is null"
}

assert {
condition = length(juju_integration.coordinator_to_s3_integrator) == 0
error_message = "Expected no s3 integration when s3_endpoint is null"
}
}

# --- S3 enabled: all resources created ---

run "s3_enabled" {
command = plan

variables {
s3_endpoint = "https://s3.example.com"
s3_access_key = "access-key"
s3_secret_key = "secret-key"
}

assert {
condition = length(juju_application.s3_integrator) == 1
error_message = "Expected s3-integrator when s3_endpoint is set"
}

assert {
condition = length(juju_secret.mimir_s3_credentials_secret) == 1
error_message = "Expected s3 credentials secret when s3_endpoint is set"
}

assert {
condition = length(juju_access_secret.mimir_s3_secret_access) == 1
error_message = "Expected s3 access secret when s3_endpoint is set"
}

assert {
condition = length(juju_integration.coordinator_to_s3_integrator) == 1
error_message = "Expected s3 integration when s3_endpoint is set"
}
}
92 changes: 92 additions & 0 deletions terraform/tests/validation.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
mock_provider "juju" {}

variables {
model_uuid = "00000000-0000-0000-0000-000000000000"
channel = "dev/edge"
}

# --- invalid worker key ---

run "invalid_worker_key" {
command = plan
expect_failures = [var.workers]

variables {
workers = {
invalid_role = {}
}
}
}

# --- role-all with other roles ---

run "all_with_other_roles" {
command = plan
expect_failures = [var.workers]

variables {
workers = {
all = {}
backend = {}
}
}
}

# --- zero units ---

run "zero_units" {
command = plan
expect_failures = [var.workers]

variables {
workers = {
backend = { units = 0 }
}
}
}

# --- partial S3 config: endpoint without credentials ---

run "s3_endpoint_without_credentials" {
command = plan
expect_failures = [var.s3_endpoint]

variables {
s3_endpoint = "https://s3.example.com"
}
}

# --- partial S3 config: credentials without endpoint ---

run "s3_credentials_without_endpoint" {
command = plan
expect_failures = [var.s3_endpoint]

variables {
s3_access_key = "access-key"
s3_secret_key = "secret-key"
}
}

# --- invalid channel track ---

run "invalid_channel" {
command = plan
expect_failures = [var.channel]

variables {
channel = "stable"
}
}

# --- anti-affinity with custom coordinator constraints ---

run "anti_affinity_with_custom_coordinator_constraints" {
command = plan
expect_failures = [var.coordinator_constraints]

variables {
anti_affinity = true
coordinator_constraints = "arch=arm64"
}
}
Loading