diff --git a/.gitignore b/.gitignore index b44de0e..5ced4fd 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ build/ parts/ prime/ stage/ +*.tfstate* +.terraform* + diff --git a/terraform/applications.tf b/terraform/applications.tf new file mode 100644 index 0000000..ca7d3a7 --- /dev/null +++ b/terraform/applications.tf @@ -0,0 +1,14 @@ +resource "juju_application" "integrator" { + model_uuid = var.model_uuid + name = var.integrator.app_name + units = var.integrator.units + + charm { + name = "data-integrator" + channel = var.integrator.channel + revision = var.integrator.revision + base = var.integrator.base + } + + config = var.integrator.config +} diff --git a/terraform/integrations.tf b/terraform/integrations.tf new file mode 100644 index 0000000..b45c50d --- /dev/null +++ b/terraform/integrations.tf @@ -0,0 +1,294 @@ +# Integrations between Kafka products + +resource "juju_integration" "kafka_kraft" { + count = local.deployment_mode == "split" ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.broker.app_name + endpoint = "peer-cluster-orchestrator" + } + + application { + name = module.controller[0].app_name + endpoint = "peer-cluster" + } +} + +resource "juju_integration" "kafka_connect" { + count = var.connect.units > 0 ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.broker.app_name + endpoint = "kafka-client" + } + + application { + name = module.connect[0].app_name + } +} + +resource "juju_integration" "kafka_karapace" { + count = var.karapace.units > 0 ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.broker.app_name + endpoint = "kafka-client" + } + + application { + name = module.karapace[0].app_name + } +} + +resource "juju_integration" "kafka_ui" { + count = var.ui.units > 0 ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.broker.app_name + endpoint = "kafka-client" + } + + application { + name = module.ui[0].app_name + } +} + + +resource "juju_integration" "karapace_ui" { + count = var.karapace.units > 0 && var.ui.units > 0 ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.karapace[0].app_name + endpoint = "karapace" + } + + application { + name = module.ui[0].app_name + } +} + +resource "juju_integration" "kafka_connect_ui" { + count = var.connect.units > 0 && var.ui.units > 0 ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.connect[0].app_name + endpoint = "connect-client" + } + + application { + name = module.ui[0].app_name + } +} + +resource "juju_integration" "integrator_kafka" { + model_uuid = var.model_uuid + + application { + name = juju_application.integrator.name + } + + application { + name = module.broker.app_name + } +} + +# TLS Integrations + +resource "juju_integration" "kafka_tls" { + count = local.tls_enabled ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.broker.app_name + endpoint = "certificates" + } + + application { + offer_url = var.tls_offer + } +} + +resource "juju_integration" "kafka_connect_tls" { + count = local.tls_enabled && var.connect.units > 0 ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.connect[0].app_name + endpoint = "certificates" + } + + application { + offer_url = var.tls_offer + } +} + +resource "juju_integration" "karapace_tls" { + count = local.tls_enabled && var.karapace.units > 0 ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.karapace[0].app_name + endpoint = "certificates" + } + + application { + offer_url = var.tls_offer + } +} + +resource "juju_integration" "kafka_ui_ingress" { + count = var.ingress_offer != null && var.ui.units > 0 ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.ui[0].app_name + endpoint = "ingress" + } + + application { + offer_url = var.ingress_offer + } +} + +# COS Integrations + +resource "juju_integration" "kafka_cos_metrics" { + count = local.cos_enabled ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.broker.app_name + endpoint = "metrics-endpoint" + } + + application { + offer_url = var.cos_offers.metrics + } + +} + +resource "juju_integration" "kafka_cos_dashboard" { + count = local.cos_enabled ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.broker.app_name + endpoint = "grafana-dashboard" + } + + application { + offer_url = var.cos_offers.dashboard + } + +} + +resource "juju_integration" "kafka_cos_logging" { + count = local.cos_enabled ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.broker.app_name + endpoint = "logging" + } + + application { + offer_url = var.cos_offers.logging + } + +} + +resource "juju_integration" "kraft_cos_metrics" { + count = local.cos_enabled && local.deployment_mode == "split" ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.controller[0].app_name + endpoint = "metrics-endpoint" + } + + application { + offer_url = var.cos_offers.metrics + } + +} + +resource "juju_integration" "kraft_cos_dashboard" { + count = local.cos_enabled && local.deployment_mode == "split" ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.controller[0].app_name + endpoint = "grafana-dashboard" + } + + application { + offer_url = var.cos_offers.dashboard + } + +} + +resource "juju_integration" "kraft_cos_logging" { + count = local.cos_enabled && local.deployment_mode == "split" ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.controller[0].app_name + endpoint = "logging" + } + + application { + offer_url = var.cos_offers.logging + } + +} + +resource "juju_integration" "connect_cos_metrics" { + count = local.cos_enabled && var.connect.units > 0 ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.connect[0].app_name + endpoint = "metrics-endpoint" + } + + application { + offer_url = var.cos_offers.metrics + } + +} + +resource "juju_integration" "connect_cos_dashboard" { + count = local.cos_enabled && var.connect.units > 0 ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.connect[0].app_name + endpoint = "grafana-dashboard" + } + + application { + offer_url = var.cos_offers.dashboard + } + +} + +resource "juju_integration" "connect_cos_logging" { + count = local.cos_enabled && var.connect.units > 0 ? 1 : 0 + model_uuid = var.model_uuid + + application { + name = module.connect[0].app_name + endpoint = "logging" + } + + application { + offer_url = var.cos_offers.logging + } + +} \ No newline at end of file diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..be87267 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,84 @@ +locals { + deployment_mode = var.controller.units > 0 ? "split" : "single" + controller_app_name = var.controller.units > 0 ? var.controller.app_name : var.broker.app_name + connect_app_name = var.connect.units > 0 ? module.connect[0].app_name : null + karapace_app_name = var.karapace.units > 0 ? module.karapace[0].app_name : null + ui_app_name = var.ui.units > 0 ? module.ui[0].app_name : null + cos_enabled = var.cos_offers.dashboard != null ? true : false + tls_enabled = var.tls_offer != null ? true : false + cos_agent_charm = "grafana-agent" + cos_agent_channel = "1/stable" +} + +module "broker" { + source = "git::https://github.com/canonical/kafka-k8s-operator//terraform?ref=main" + model_uuid = var.model_uuid + app_name = var.broker.app_name + channel = var.broker.channel + revision = var.broker.revision + constraints = var.broker.constraints + base = var.broker.base + units = var.broker.units + storage = var.broker.storage + config = merge(var.broker.config, { + profile = var.profile + roles = local.deployment_mode == "single" ? "broker,controller" : "broker" + }) +} + +module "controller" { + count = local.deployment_mode == "split" ? 1 : 0 + source = "git::https://github.com/canonical/kafka-k8s-operator//terraform?ref=main" + model_uuid = var.model_uuid + app_name = var.controller.app_name + channel = var.controller.channel + revision = var.controller.revision + constraints = var.controller.constraints + base = var.controller.base + units = var.controller.units + storage = var.controller.storage + config = merge(var.controller.config, { + profile = var.profile + roles = "controller" + }) +} + + +module "connect" { + count = var.connect.units > 0 ? 1 : 0 + source = "git::https://github.com/canonical/kafka-connect-k8s-operator//terraform?ref=main" + model_uuid = var.model_uuid + app_name = var.connect.app_name + channel = var.connect.channel + revision = var.connect.revision + constraints = var.connect.constraints + base = var.connect.base + units = var.connect.units + config = var.connect.config +} + +module "karapace" { + count = var.karapace.units > 0 ? 1 : 0 + source = "git::https://github.com/canonical/karapace-k8s-operator//terraform?ref=main" + model_uuid = var.model_uuid + app_name = var.karapace.app_name + channel = var.karapace.channel + revision = var.karapace.revision + constraints = var.karapace.constraints + base = var.karapace.base + units = var.karapace.units + config = var.karapace.config +} + +module "ui" { + count = var.ui.units > 0 ? 1 : 0 + source = "git::https://github.com/canonical/kafka-ui-k8s-operator//terraform?ref=main" + model_uuid = var.model_uuid + app_name = var.ui.app_name + channel = var.ui.channel + revision = var.ui.revision + constraints = var.ui.constraints + base = var.ui.base + units = var.ui.units + config = var.ui.config +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..293894b --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,25 @@ +output "offers" { + description = "List of offers URLs." + value = merge( + { + kafka-client = module.broker.offers.kafka-client + }, + { + connect-client = var.connect.units > 0 ? module.connect[0].offers.connect-client : null + }, + { + karapace-client = var.karapace.units > 0 ? module.karapace[0].offers.karapace-client : null + } + ) +} + +output "app_names" { + description = "Output of all deployed application names." + value = { + broker = module.broker.app_name + controller = local.controller_app_name, + connect = local.connect_app_name, + karapace = local.karapace_app_name, + ui = local.ui_app_name + } +} \ No newline at end of file diff --git a/terraform/providers.tf b/terraform/providers.tf new file mode 100644 index 0000000..4b61781 --- /dev/null +++ b/terraform/providers.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">=1.7.3" + + required_providers { + juju = { + version = ">=1.0.0" + source = "juju/juju" + } + } +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..6c0cb5f --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,148 @@ +variable "model_uuid" { + description = "The Juju Model UUID to deploy to" + type = string +} + +variable "profile" { + description = "The deployment profile to use, either 'production' or 'testing'" + type = string + default = "testing" +} + +variable "tls_offer" { + description = "TLS Provider endpoint to be used on Client relations." + type = string + default = null +} + +variable "ingress_offer" { + description = "Ingress provider endpoint to be used on for the Kafka UI." + type = string + default = null +} + +variable "cos_offers" { + description = "COS offers for observability." + type = object({ + dashboard = optional(string, null), + metrics = optional(string, null), + logging = optional(string, null), + tracing = optional(string, null) + }) + + default = {} + + validation { + condition = (( + var.cos_offers.dashboard != null && + var.cos_offers.metrics != null && + var.cos_offers.logging != null + ) || ( + var.cos_offers.dashboard == null && + var.cos_offers.metrics == null && + var.cos_offers.logging == null + )) + error_message = "Either all or none of the COS offers should be provided: 'dashboard', 'metrics', 'logging'." + } +} + +variable "broker" { + description = "Defines the Apache Kafka broker application configuration" + type = object({ + app_name = optional(string, "kafka-broker") + channel = optional(string, "4/edge") + config = optional(map(string), {}) + constraints = optional(string, "arch=amd64") + resources = optional(map(string), {}) + revision = optional(number, null) + base = optional(string, "ubuntu@24.04") + units = optional(number, 3) + storage = optional(map(string), {}) + }) + default = {} +} + +variable "controller" { + description = "Defines the Apache Kafka KRaft controller application configuration" + type = object({ + app_name = optional(string, "kafka-controller") + channel = optional(string, "4/edge") + config = optional(map(string), {}) + constraints = optional(string, "arch=amd64") + resources = optional(map(string), {}) + revision = optional(number, null) + base = optional(string, "ubuntu@24.04") + units = optional(number, 3) + storage = optional(map(string), {}) + }) + default = {} + + validation { + condition = var.controller.units == 0 || var.controller.units % 2 != 0 + error_message = "The number of Apache Kafka KRaft controllers must be odd (e.g., 1, 3, 5, ...)." + } +} + + +variable "connect" { + description = "Defines the Kafka Connect application configuration" + type = object({ + app_name = optional(string, "kafka-connect") + channel = optional(string, "latest/edge") + config = optional(map(string), {}) + constraints = optional(string, "arch=amd64") + resources = optional(map(string), {}) + revision = optional(number, null) + base = optional(string, "ubuntu@22.04") + units = optional(number, 1) + }) + default = {} +} + +variable "karapace" { + description = "Defines the Karapace application configuration" + type = object({ + app_name = optional(string, "karapace") + channel = optional(string, "latest/edge") + config = optional(map(string), {}) + constraints = optional(string, "arch=amd64") + resources = optional(map(string), {}) + revision = optional(number, null) + base = optional(string, "ubuntu@24.04") + units = optional(number, 1) + }) + default = {} +} + +variable "ui" { + description = "Defines the Kafbat Kafka UI application configuration" + type = object({ + app_name = optional(string, "kafka-ui") + channel = optional(string, "latest/edge") + config = optional(map(string), {}) + constraints = optional(string, "arch=amd64") + resources = optional(map(string), {}) + revision = optional(number, null) + base = optional(string, "ubuntu@24.04") + units = optional(number, 1) + }) + default = {} +} + +variable "integrator" { + description = "Defines the Integrator application configuration" + type = object({ + app_name = optional(string, "data-integrator") + channel = optional(string, "latest/edge") + config = optional(map(string), { + topic-name = "__admin-user" + extra-user-roles = "admin" + }) + constraints = optional(string, "arch=amd64") + resources = optional(map(string), {}) + revision = optional(number, null) + base = optional(string, "ubuntu@24.04") + units = optional(number, 1) + }) + default = {} +}