Skip to content

Commit

Permalink
Add Optional Helm Install Support
Browse files Browse the repository at this point in the history
  • Loading branch information
bobbyiliev committed Dec 22, 2024
1 parent d7c67fa commit 38e54d6
Show file tree
Hide file tree
Showing 10 changed files with 421 additions and 4 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ The module has been tested with:
| <a name="module_database"></a> [database](#module\_database) | ./modules/database | n/a |
| <a name="module_eks"></a> [eks](#module\_eks) | ./modules/eks | n/a |
| <a name="module_networking"></a> [networking](#module\_networking) | ./modules/networking | n/a |
| <a name="module_operator"></a> [operator](#module\_operator) | ./modules/operator | n/a |
| <a name="module_storage"></a> [storage](#module\_storage) | ./modules/storage | n/a |

## Resources
Expand All @@ -44,6 +45,7 @@ The module has been tested with:
| [aws_iam_role_policy.materialize_s3](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_iam_user.materialize](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource |
| [aws_iam_user_policy.materialize_s3](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy) | resource |
| [aws_eks_cluster_auth.cluster](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/eks_cluster_auth) | data source |

## Inputs

Expand All @@ -67,8 +69,10 @@ The module has been tested with:
| <a name="input_enable_cluster_creator_admin_permissions"></a> [enable\_cluster\_creator\_admin\_permissions](#input\_enable\_cluster\_creator\_admin\_permissions) | To add the current caller identity as an administrator | `bool` | `true` | no |
| <a name="input_enable_monitoring"></a> [enable\_monitoring](#input\_enable\_monitoring) | Enable CloudWatch monitoring | `bool` | `true` | no |
| <a name="input_environment"></a> [environment](#input\_environment) | Environment name (e.g., prod, staging, dev) | `string` | n/a | yes |
| <a name="input_install_materialize_operator"></a> [install\_materialize\_operator](#input\_install\_materialize\_operator) | Whether to install the Materialize operator | `bool` | `false` | no |
| <a name="input_kubernetes_namespace"></a> [kubernetes\_namespace](#input\_kubernetes\_namespace) | The Kubernetes namespace for the Materialize resources | `string` | `"materialize-environment"` | no |
| <a name="input_log_group_name_prefix"></a> [log\_group\_name\_prefix](#input\_log\_group\_name\_prefix) | Prefix for the CloudWatch log group name (will be combined with environment name) | `string` | `"materialize"` | no |
| <a name="input_materialize_instances"></a> [materialize\_instances](#input\_materialize\_instances) | Configuration for Materialize instances | <pre>list(object({<br/> name = string<br/> instance_id = string<br/> namespace = optional(string)<br/> database_name = optional(string)<br/> database_username = optional(string)<br/> database_password = optional(string)<br/> database_host = optional(string)<br/> cpu_request = optional(string)<br/> memory_request = optional(string)<br/> memory_limit = optional(string)<br/> }))</pre> | `[]` | no |
| <a name="input_metrics_retention_days"></a> [metrics\_retention\_days](#input\_metrics\_retention\_days) | Number of days to retain CloudWatch metrics | `number` | `7` | no |
| <a name="input_namespace"></a> [namespace](#input\_namespace) | Namespace for all resources, usually the organization or project name | `string` | n/a | yes |
| <a name="input_network_id"></a> [network\_id](#input\_network\_id) | The ID of the VPC in which resources will be deployed. Only used if create\_vpc is false. | `string` | `""` | no |
Expand Down
26 changes: 26 additions & 0 deletions examples/simple/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module "materialize_infrastructure" {
# EKS Configuration
cluster_version = "1.31"
# node_group_instance_types = ["m6g.medium"]
# TODO: Defaulting to a smaller instance type due to resource constraints
node_group_instance_types = ["r5.xlarge"]
node_group_desired_size = 2
node_group_min_size = 1
Expand All @@ -47,6 +48,31 @@ module "materialize_infrastructure" {
enable_monitoring = true
metrics_retention_days = 3

# Enable and configure Materialize operator
install_materialize_operator = true

# Configure Materialize instances
materialize_instances = [
{
name = "analytics"
instance_id = "12345678-1234-1234-1234-123456789012"
namespace = "materialize-environment"
database_name = "analytics_db"
database_username = "materialize"
database_password = var.database_password
database_host = module.materialize_infrastructure.database_endpoint
},
{
name = "production"
instance_id = "87654321-4321-4321-4321-210987654321"
namespace = "materialize-environment"
database_name = "production_db"
database_username = "materialize"
database_password = var.database_password
database_host = module.materialize_infrastructure.database_endpoint
}
]

# Tags
tags = {
Environment = "dev"
Expand Down
48 changes: 48 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,54 @@ module "database" {
tags = local.common_tags
}

module "operator" {
count = var.install_materialize_operator ? 1 : 0
source = "./modules/operator"

namespace = var.namespace
environment = var.environment
instances = var.materialize_instances
iam_role_arn = aws_iam_role.materialize_s3.arn
cluster_endpoint = module.eks.cluster_endpoint
cluster_ca_certificate = module.eks.cluster_certificate_authority_data
s3_bucket_name = module.storage.bucket_name

providers = {
kubernetes = kubernetes,
helm = helm
}

depends_on = [
module.eks,
module.storage,
module.database
]
}

locals {
network_id = var.create_vpc ? module.networking.vpc_id : var.network_id
network_private_subnet_ids = var.create_vpc ? module.networking.private_subnet_ids : var.network_private_subnet_ids

# instance_backend_urls = {
# for instance in var.materialize_instances : instance.name => {
# metadata_backend_url = format(
# "postgres://%s:%s@%s/%s?sslmode=require",
# coalesce(instance.database_username, var.database_username),
# coalesce(instance.database_password, var.database_password),
# module.database.db_instance_endpoint,
# coalesce(instance.database_name, "${instance.name}_db")
# )
# persist_backend_url = format(
# "s3://%s/%s/%s:serviceaccount:%s:%s",
# module.storage.bucket_name,
# var.environment,
# instance.name,
# coalesce(instance.namespace, "materialize-environment"),
# instance.instance_id
# )
# }
# }

# Common tags that apply to all resources
common_tags = merge(
var.tags,
Expand Down Expand Up @@ -191,3 +235,7 @@ resource "aws_iam_role_policy" "materialize_s3" {
locals {
name_prefix = "${var.namespace}-${var.environment}"
}

data "aws_eks_cluster_auth" "cluster" {
name = module.eks.cluster_name
}
188 changes: 188 additions & 0 deletions modules/operator/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
locals {
name_prefix = "${var.namespace}-${var.environment}"
}

resource "kubernetes_namespace" "materialize" {
metadata {
name = var.operator_namespace
}
}

resource "kubernetes_namespace" "instance_namespaces" {
for_each = toset(compact([for instance in var.instances : instance.namespace if instance.namespace != null]))

metadata {
name = each.key
}
}

resource "helm_release" "materialize_operator" {
name = "${local.name_prefix}-operator"
namespace = kubernetes_namespace.materialize.metadata[0].name
# TODO: Publish the chart to a public repository, currently using a forked version of the chart
repository = "https://raw.githubusercontent.com/bobbyiliev/materialize/refs/heads/helm-chart-package/misc/helm-charts"
chart = "materialize-operator"
# version = var.operator_version
# TODO: Use the latest version of the chart
version = "v25.1.0-beta.1"

values = [
yamlencode({
operator ={
cloudProvider = {
type = "aws"
region = data.aws_region.current.name
providers = {
aws = {
enabled = true
accountID = data.aws_caller_identity.current.account_id
iam = {
roles = {
environment = var.iam_role_arn
}
}
}
}
}
}
})
]

depends_on = [kubernetes_namespace.materialize]
}

resource "kubernetes_secret" "materialize_backends" {
for_each = { for idx, instance in var.instances : instance.name => instance }

metadata {
name = "${each.key}-materialize-backend"
namespace = coalesce(each.value.namespace, var.operator_namespace)
}

data = {
metadata_backend_url = format(
"postgres://%s:%s@%s/%s?sslmode=require",
each.value.database_username,
each.value.database_password,
each.value.database_host,
coalesce(each.value.database_name, "${each.key}_db")
)
persist_backend_url = format(
"s3://%s/%s-%s:serviceaccount:%s:%s",
var.s3_bucket_name,
var.environment,
each.key,
coalesce(each.value.namespace, kubernetes_namespace.materialize.metadata[0].name),
each.value.instance_id
)
}
}

resource "kubernetes_manifest" "materialize_instances" {
for_each = { for idx, instance in var.instances : instance.name => instance }

manifest = {
apiVersion = "materialize.cloud/v1alpha1"
kind = "Materialize"
metadata = {
name = each.value.instance_id
namespace = coalesce(each.value.namespace, var.operator_namespace)
}
spec = {
environmentdImageRef = "materialize/environmentd:${var.operator_version}"
backendSecretName = "${each.key}-materialize-backend"
environmentdResourceRequirements = {
limits = {
memory = each.value.memory_limit
}
requests = {
cpu = each.value.cpu_request
memory = each.value.memory_request
}
}
balancerdResourceRequirements = {
limits = {
memory = "256Mi"
}
requests = {
cpu = "100m"
memory = "256Mi"
}
}
}
}

depends_on = [
helm_release.materialize_operator,
kubernetes_secret.materialize_backends,
kubernetes_namespace.instance_namespaces
]
}

# Materialize does not currently create databases within the instances, so we need to create them ourselves
resource "kubernetes_manifest" "db_init_configmap" {
for_each = { for idx, instance in var.instances : instance.name => instance }

manifest = {
apiVersion = "v1"
kind = "ConfigMap"
metadata = {
name = "init-db-script-${each.key}"
namespace = coalesce(each.value.namespace, var.operator_namespace)
}
data = {
"init.sql" = format(
"CREATE DATABASE IF NOT EXISTS %s;",
coalesce(each.value.database_name, "${each.key}_db")
)
}
}
}

resource "kubernetes_manifest" "db_init_job" {
for_each = { for idx, instance in var.instances : instance.name => instance }

manifest = {
apiVersion = "batch/v1"
kind = "Job"
metadata = {
name = "create-db-${each.key}"
namespace = coalesce(each.value.namespace, var.operator_namespace)
}
spec = {
template = {
spec = {
containers = [{
name = "init-db"
image = "postgres:latest"
command = [
"/bin/sh",
"-c",
format(
"psql $DATABASE_URL -c \"CREATE DATABASE %s;\"",
coalesce(each.value.database_name, "${each.key}_db")
)
]
env = [
{
name = "DATABASE_URL"
value = format(
"postgres://%s:%s@%s/%s?sslmode=require",
each.value.database_username,
each.value.database_password,
each.value.database_host,
"postgres" # Default database to connect to
)
}
]
}]
restartPolicy = "OnFailure"
}
}
backoffLimit = 3
}
}
}

data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
24 changes: 24 additions & 0 deletions modules/operator/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
output "operator_namespace" {
description = "Namespace where the operator is installed"
value = kubernetes_namespace.materialize.metadata[0].name
}

output "operator_release_name" {
description = "Helm release name of the operator"
value = helm_release.materialize_operator.name
}

output "operator_release_status" {
description = "Status of the helm release"
value = helm_release.materialize_operator.status
}

output "materialize_instances" {
description = "Details of created Materialize instances"
value = {
for name, instance in kubernetes_manifest.materialize_instances : name => {
id = instance.manifest.metadata.name
namespace = instance.manifest.metadata.namespace
}
}
}
Loading

0 comments on commit 38e54d6

Please sign in to comment.