diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 83afe7018..b6d6c0d8d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,6 +32,53 @@ jobs: - name: Run tests run: tox run -e unit + terraform-test: + name: Terraform - Lint and Simple Deployment + runs-on: ubuntu-latest + timeout-minutes: 120 + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: lint charm module + working-directory: ./terraform + run: | + terraform init + terraform fmt + terraform validate + - name: lint test charm module + working-directory: ./terraform/tests + run: | + terraform init + terraform fmt + terraform validate + - name: run checks - prepare + run: | + sudo snap install juju --channel=3.6/beta --classic + sudo snap install juju-wait --channel=latest/stable --classic + sudo snap install jq + - name: LXD setup + run: | + sudo snap refresh lxd --channel=latest/stable + sudo adduser "$USER" 'lxd' + # `newgrp` does not work in GitHub Actions; use `sg` instead + sg 'lxd' -c "lxd waitready" + sg 'lxd' -c "lxd init --auto" + sg 'lxd' -c "lxc network set lxdbr0 ipv6.address none" + sudo iptables -F FORWARD + sudo iptables -P FORWARD ACCEPT + - name: Juju setup + run: | + sg 'lxd' -c "juju bootstrap 'localhost' --config model-logs-size=10G" + juju model-defaults logging-config='=INFO; unit=DEBUG' + juju add-model test + - name: Terraform deploy + working-directory: ./terraform/tests/ + run: | + terraform apply -var "model=test" -target null_resource.simple_deployment_juju_wait_deployment -auto-approve + lib-check: name: Check libraries runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 5460d3af7..b42f19aea 100644 --- a/.gitignore +++ b/.gitignore @@ -52,8 +52,52 @@ override.tf.json # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan # example: *tfplan* +# Ignore CLI configuration files + + +######################################################## +# +# Terraform .gitignore +# +######################################################## + + +# Local .terraform directories +**/.terraform/* +*.terraform.lock.hcl + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Generated files +*.key +credentials* + +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars +*.tfvars.json + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + # Ignore CLI configuration files .terraformrc terraform.rc -.terraform.lock.hcl - diff --git a/terraform/README.md b/terraform/README.md index fdd0cdd1c..cf95115db 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -1,58 +1,66 @@ -# MongoDB Operator Terraform module +# Terraform module for mongodb-operator -This folder contains a base [Terraform][Terraform] module for the `mongodb` charm. +This is a Terraform module facilitating the deployment of the MongoDB charm with [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). -The module uses the [Terraform Juju provider][Terraform Juju provider] to model the charm deployment onto any Kubernetes environment managed by [Juju][Juju]. +## Requirements +This module requires a `juju` model to be available. Refer to the [usage section](#usage) below for more details. -The base module is not intended to be deployed in separation (it is possible though), but should rather serve as a building block for higher level modules. +## API -## Module structure +### Inputs +The module offers the following configurable inputs: -- **main.tf** - Defines the Juju application to be deployed. -- **variables.tf** - Allows customization of the deployment such as Juju model name, channel or application name and charm configuration. -- **output.tf** - Responsible for integrating the module with other Terraform modules, primarily by defining potential integration endpoints (charm integrations), but also by exposing the application name. -- **terraform.tf** - Defines the Terraform provider. +| Name | Type | Description | Required | +| - | - | - | - | +| `app_name`| string | Application name | False | +| `channel`| string | Channel that the charm is deployed from | False | +| `base`| string | The series to be used for this charm | False | +| `config`| map(string) | Map of the charm configuration options | False | +| `model_name`| string | Name of the model that the charm is deployed on | True | +| `resources`| map(string) | Map of the charm resources | False | +| `revision`| number | Revision number of the charm name | False | +| `units`| number | Number of units to be deployed | False | +| `constraints`| string | Machine constraints for the charm | False | +| `storage`| map(string) | Storage description, must follow the juju provider schema | False | -## Using mongodb base module in higher level modules -If you want to use `mongodb-operator` base module as part of your Terraform module, import it like shown below. +### Outputs +Upon applied, the module exports the following outputs: -```text -module "mongodb-operator" { - source = "git::https://github.com/canonical/mongodb-operator.git/terraform" - - model_name = "juju_model_name" - - (Customize configuration variables here if needed) -} -``` +| Name | Description | +| - | - | +| `app_name`| Application name | +| `provides`| Map of `provides` endpoints | +| `requires`| Map of `requires` endpoints | -Please see the link to customize the Grafana configuration variables if needed. +## Usage -- [MongoDB configuration options][MongoDB configuration options] +This module is intended to be used as part of a higher-level module. When defining one, users should ensure that Terraform is aware of the `juju_model` dependency of the charm module. There are two options to do so when creating a high-level module: -Create the integrations, for instance: +### Define a `juju_model` resource +Define a `juju_model` resource and pass to the `model_name` input a reference to the `juju_model` resource's name. For example: -```text -resource "juju_integration" "amf-db" { - model = var.model_name - - application { - name = module.amf.app_name - endpoint = module.amf.database_endpoint - } +``` +resource "juju_model" "mongodb" { + name = mongodb +} - application { - name = module.mongodb.app_name - endpoint = module.mongodb.database_endpoint - } +module "mongodb-operator" { + source = "" + model_name = juju_model.mongodb.name } ``` -Please check the available [integration pairs][integration pairs]. +### Define a `data` source +Define a `data` source and pass to the `model_name` input a reference to the `data.juju_model` resource's name. This will enable Terraform to look for a `juju_model` resource with a name attribute equal to the one provided, and apply only if this is present. Otherwise, it will fail before applying anything. -[Terraform]: https://www.terraform.io/ -[Juju]: https://juju.is -[Terraform Juju provider]: https://registry.terraform.io/providers/juju/juju/latest -[MongoDB configuration options]: https://charmhub.io/mongodb/configure?channel=6/edge -[integration pairs]: https://charmhub.io/mongodb/integrations?channel=6/edge +``` +data "juju_model" "mongodb" { + name = var.model_name +} + +module "mongodb" { + source = "" + model_name = data.juju_model.mongodb.name +} +``` \ No newline at end of file diff --git a/terraform/main.tf b/terraform/main.tf index 7d9f417ad..bf330f69c 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -2,15 +2,40 @@ # See LICENSE file for licensing details. resource "juju_application" "mongodb" { - name = var.app_name - model = var.model_name charm { - name = "mongodb" - channel = var.channel - base = "ubuntu@22.04" + name = "mongodb" + channel = var.channel + revision = var.revision + base = "ubuntu@22.04" } - config = var.config - units = 1 - trust = true -} + config = var.config + model = var.model + name = var.app_name + units = var.units + constraints = var.constraints + + + # TODO: uncomment once final fixes have been added for: + # Error: juju/terraform-provider-juju#443, juju/terraform-provider-juju#182 + # placement = join(",", var.machines) + + endpoint_bindings = [ + for k, v in var.endpoint_bindings : { + endpoint = k, space = v + } + ] + + storage_directives = var.storage + + lifecycle { + precondition { + condition = length(var.machines) == 0 || length(var.machines) == var.units + error_message = "Machine count does not match unit count" + } + precondition { + condition = length(var.storage) == 0 || lookup(var.storage, "count", 0) <= 1 + error_message = "Only one storage is supported" + } + } +} \ No newline at end of file diff --git a/terraform/outputs.tf b/terraform/outputs.tf index 943449446..89f7747aa 100644 --- a/terraform/outputs.tf +++ b/terraform/outputs.tf @@ -13,11 +13,6 @@ output "database_endpoint" { value = "database" } -output "obsolete_endpoint" { - description = "Name of the endpoint to provide the mongodb interface." - value = "obsolete" -} - output "cos_agent_endpoint" { description = "Name of the endpoint to provide the cos_agent interface." value = "cos-agent" @@ -33,22 +28,6 @@ output "cluster_endpoint" { value = "cluster" } -output "metrics_endpoint" { - description = "Name of the endpoint to provide the prometheus_scrape interface." - value = "metrics-endpoint" -} - -output "grafana_dashboard_endpoint" { - description = "Name of the endpoint to provide the grafana_dashboard interface." - value = "grafana-dashboard" -} - -output "logging_endpoint" { - description = "Name of the endpoint to provide the loki_push_api relation interface." - value = "logging" -} - - # Required integration endpoints output "certificates_endpoint" { @@ -64,4 +43,4 @@ output "s3_credentials_endpoint" { output "sharding_endpoint" { description = "Name of the endpoint to provide the shards interface." value = "sharding" -} +} \ No newline at end of file diff --git a/terraform/tests/preamble.tf b/terraform/tests/preamble.tf new file mode 100644 index 000000000..5c0f8cb8d --- /dev/null +++ b/terraform/tests/preamble.tf @@ -0,0 +1,25 @@ +resource "null_resource" "preamble" { + provisioner "local-exec" { + command = <<-EOT + sudo snap install juju-wait --classic || true + EOT + } +} + +resource "juju_application" "self-signed-certificates" { + charm { + name = "self-signed-certificates" + channel = "latest/stable" + } + model = var.model_name + depends_on = [null_resource.preamble] +} + +resource "juju_application" "data-integrator" { + charm { + name = "data-integrator" + channel = "latest/stable" + } + model = var.model_name + depends_on = [null_resource.preamble] +} diff --git a/terraform/tests/providers.tf b/terraform/tests/providers.tf new file mode 100644 index 000000000..3be3d8566 --- /dev/null +++ b/terraform/tests/providers.tf @@ -0,0 +1,16 @@ +terraform { + required_providers { + juju = { + source = "juju/juju" + version = "~> 0.14.0" + } + http = { + source = "hashicorp/http" + version = "~> 3.4.5" + } + external = { + source = "hashicorp/external" + version = "~> 2.3.4" + } + } +} diff --git a/terraform/tests/simple_deployment.tf b/terraform/tests/simple_deployment.tf new file mode 100644 index 000000000..74e3ba041 --- /dev/null +++ b/terraform/tests/simple_deployment.tf @@ -0,0 +1,49 @@ +module "mongodb" { + source = "../" + app_name = var.app_name + model = var.model_name + units = var.simple_mongodb_units + channel = "6/edge" +} + +resource "juju_integration" "simple_deployment_tls-operator_mongodb-integration" { + model = var.model_name + + application { + name = juju_application.self-signed-certificates.name + } + application { + name = var.app_name + } + depends_on = [ + juju_application.self-signed-certificates, + module.mongodb + ] + +} + +resource "juju_integration" "simple_deployment_data-integrator_mongodb-integration" { + model = var.model_name + + application { + name = juju_application.data-integrator.name + } + application { + name = var.app_name + } + depends_on = [ + juju_application.data-integrator, + module.mongodb + ] + +} + +resource "null_resource" "simple_deployment_juju_wait_deployment" { + provisioner "local-exec" { + command = <<-EOT + juju-wait -v --model ${var.model_name} + EOT + } + + depends_on = [juju_integration.simple_deployment_tls-operator_mongodb-integration] +} diff --git a/terraform/tests/variables.tf b/terraform/tests/variables.tf new file mode 100644 index 000000000..53d7f0851 --- /dev/null +++ b/terraform/tests/variables.tf @@ -0,0 +1,16 @@ +variable "model_name" { + description = "Model name" + type = string +} + +variable "app_name" { + description = "mongodb app name" + type = string + default = "mongodb" +} + +variable "simple_mongodb_units" { + description = "Node count" + type = number + default = 1 +} diff --git a/terraform/variables.tf b/terraform/variables.tf index 5c611dfcd..1be4c0749 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -1,26 +1,63 @@ # Copyright 2024 Canonical Ltd. # See LICENSE file for licensing details. -variable "model_name" { - description = "Name of Juju model to deploy application to" - type = string - default = "" -} - variable "app_name" { - description = "Name of the application in the Juju model" + description = "Application name" type = string default = "mongodb" } variable "channel" { - description = "The channel to use when deploying a charm" + description = "Charm channel" type = string - default = "6/beta" + default = "6/stable" } + + variable "config" { - description = "Additional configuration for the MongoDB. Details about available options can be found at https://charmhub.io/mongodb/configure?channel=6/beta." + description = "Map of charm configuration options" + type = map(string) + default = {} +} + +variable "model" { + description = "Model name" + type = string +} + +variable "revision" { + description = "Charm revision" + type = number + default = null +} + +variable "units" { + description = "Charm units" + type = number + default = 3 +} + +variable "constraints" { + description = "String listing constraints for this application" + type = string + default = "arch=amd64" +} + +variable "machines" { + description = "List of machines for placement" + type = list(string) + default = [] +} + +variable "storage" { + description = "Map of storage used by the application" + type = map(string) + default = {} +} + +variable "endpoint_bindings" { + description = "Map of endpoint bindings" type = map(string) default = {} } \ No newline at end of file diff --git a/terraform/terraform.tf b/terraform/versions.tf similarity index 73% rename from terraform/terraform.tf rename to terraform/versions.tf index 4f60bb46b..3d6e5a5f2 100644 --- a/terraform/terraform.tf +++ b/terraform/versions.tf @@ -2,10 +2,11 @@ # See LICENSE file for licensing details. terraform { + required_version = ">= 1.6" required_providers { juju = { source = "juju/juju" - version = "~> 0.10.1" + version = "~> 0.14.0" } } -} +} \ No newline at end of file