From 54c705b8493dd32dd065f3ea541473aa1d7d4193 Mon Sep 17 00:00:00 2001 From: Marcin Cuber Date: Mon, 15 Jun 2026 14:55:21 +0100 Subject: [PATCH 1/2] add new variables --- README.md | 9 +++- examples/advanced/README.md | 4 ++ examples/advanced/main.tf | 20 +++++--- examples/advanced/variables.tf | 59 ++++++++++++++++++++++ logs.tf | 1 + main.tf | 61 +++++++++++++++++++++-- tests/customization.tftest.hcl | 90 +++++++++++++++++++++++++++++++--- tests/validation.tftest.hcl | 41 ++++++++++++++++ variables.tf | 75 +++++++++++++++++++++++++++- 9 files changed, 342 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index ccb0dc0..dff27a5 100644 --- a/README.md +++ b/README.md @@ -80,15 +80,20 @@ aws eks update-kubeconfig --name dev-eks | Name | Description | Type | Default | Required | | ---- | ----------- | ---- | ------- | :------: | -| [addons](#input\_addons) | EKS add-ons to install after the managed node groups are created. |
map(object({
configuration_values = optional(string)
resolve_conflicts_on_create = optional(string, "OVERWRITE")
resolve_conflicts_on_update = optional(string, "OVERWRITE")
service_account_role_arn = optional(string)
version = optional(string)
}))
|
{
"coredns": {},
"kube-proxy": {},
"vpc-cni": {}
}
| no | +| [access\_config](#input\_access\_config) | Optional EKS access configuration for cluster authentication mode and creator admin permissions. |
object({
authentication_mode = optional(string)
bootstrap_cluster_creator_admin_permissions = optional(bool)
})
| `null` | no | +| [addons](#input\_addons) | EKS add-ons to install after the managed node groups are created. |
map(object({
configuration_values = optional(string)
pod_identity_associations = optional(list(object({
role_arn = string
service_account = string
})), [])
resolve_conflicts_on_create = optional(string, "OVERWRITE")
resolve_conflicts_on_update = optional(string, "OVERWRITE")
service_account_role_arn = optional(string)
version = optional(string)
}))
|
{
"coredns": {},
"kube-proxy": {},
"vpc-cni": {}
}
| no | +| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | Optional KMS key ID or ARN for encrypting the EKS control plane CloudWatch log group. | `string` | `null` | no | | [cloudwatch\_log\_retention\_days](#input\_cloudwatch\_log\_retention\_days) | Retention in days for the EKS control plane CloudWatch log group. | `number` | `30` | no | +| [cluster\_encryption\_config](#input\_cluster\_encryption\_config) | Optional EKS encryption configuration for Kubernetes secrets using an existing KMS key. |
object({
provider_key_arn = string
resources = optional(list(string), ["secrets"])
})
| `null` | no | | [cluster\_name](#input\_cluster\_name) | Optional EKS cluster name. When null, name is used as the cluster name. | `string` | `null` | no | +| [cluster\_security\_group\_ids](#input\_cluster\_security\_group\_ids) | Additional security group IDs to associate with the EKS control plane. | `list(string)` | `[]` | no | +| [deletion\_protection](#input\_deletion\_protection) | Whether to enable deletion protection for the EKS cluster. Leave null to use the AWS/provider default. | `bool` | `null` | no | | [enabled\_cluster\_log\_types](#input\_enabled\_cluster\_log\_types) | EKS control plane log types to enable. | `list(string)` |
[
"api",
"audit",
"authenticator"
]
| no | | [endpoint\_private\_access](#input\_endpoint\_private\_access) | Whether the Kubernetes API server endpoint is reachable from within the VPC. | `bool` | `true` | no | | [endpoint\_public\_access](#input\_endpoint\_public\_access) | Whether the Kubernetes API server endpoint is reachable from the public internet. | `bool` | `true` | no | | [kubernetes\_version](#input\_kubernetes\_version) | Kubernetes version for the EKS cluster and managed node groups. Leave null to use the current AWS default. | `string` | `null` | no | | [name](#input\_name) | Name prefix for module-created resources. Used as the EKS cluster name when cluster\_name is null. | `string` | n/a | yes | -| [node\_groups](#input\_node\_groups) | Managed node groups to create. |
map(object({
ami_type = optional(string)
capacity_type = optional(string, "ON_DEMAND")
desired_size = optional(number, 2)
disk_size = optional(number, 20)
instance_types = optional(list(string), ["t3.medium"])
labels = optional(map(string), {})
max_size = optional(number, 3)
min_size = optional(number, 1)
subnet_ids = optional(list(string), [])
update_max_unavailable = optional(number, 1)
taints = optional(list(object({
effect = string
key = string
value = optional(string, "")
})), [])
}))
|
{
"default": {}
}
| no | +| [node\_groups](#input\_node\_groups) | Managed node groups to create. |
map(object({
ami_type = optional(string)
capacity_type = optional(string, "ON_DEMAND")
desired_size = optional(number, 2)
disk_size = optional(number, 20)
instance_types = optional(list(string), ["t3.medium"])
labels = optional(map(string), {})
max_size = optional(number, 3)
min_size = optional(number, 1)
subnet_ids = optional(list(string), [])
update_max_unavailable = optional(number, 1)
node_repair_config = optional(object({
enabled = optional(bool)
max_parallel_nodes_repaired_count = optional(number)
max_parallel_nodes_repaired_percentage = optional(number)
max_unhealthy_node_threshold_count = optional(number)
max_unhealthy_node_threshold_percentage = optional(number)
overrides = optional(list(object({
min_repair_wait_time_mins = number
node_monitoring_condition = string
node_unhealthy_reason = string
repair_action = string
})), [])
}))
taints = optional(list(object({
effect = string
key = string
value = optional(string, "")
})), [])
}))
|
{
"default": {}
}
| no | | [public\_access\_cidrs](#input\_public\_access\_cidrs) | CIDR blocks that can access the public Kubernetes API endpoint. | `list(string)` |
[
"0.0.0.0/0"
]
| no | | [service\_ipv4\_cidr](#input\_service\_ipv4\_cidr) | Optional Kubernetes service IPv4 CIDR. Set only when you need a non-default service CIDR. | `string` | `null` | no | | [subnet\_ids](#input\_subnet\_ids) | Subnet IDs for the EKS control plane and default node groups. Use at least two subnets in different Availability Zones. | `list(string)` | n/a | yes | diff --git a/examples/advanced/README.md b/examples/advanced/README.md index 9b7ef7f..c99391e 100644 --- a/examples/advanced/README.md +++ b/examples/advanced/README.md @@ -5,8 +5,12 @@ More complete example for teams that want to tune the cluster while still supply This example shows: - Restricted public Kubernetes API access. +- Optional additional EKS control plane security groups. +- Access entry authentication mode configuration and deletion protection. +- Optional KMS encryption for Kubernetes secrets and control-plane logs. - Full control-plane logging with configurable retention. - Separate on-demand and spot managed node groups. +- Managed node group node repair configuration. - Optional node-group subnet overrides. - Separate module resource prefix and EKS cluster name through `name` and `cluster_name`. - VPC CNI prefix delegation configuration. diff --git a/examples/advanced/main.tf b/examples/advanced/main.tf index d5c6907..42bba43 100644 --- a/examples/advanced/main.tf +++ b/examples/advanced/main.tf @@ -21,13 +21,19 @@ module "eks" { kubernetes_version = var.kubernetes_version subnet_ids = var.control_plane_subnet_ids - endpoint_private_access = true - endpoint_public_access = var.endpoint_public_access - public_access_cidrs = var.public_access_cidrs + cluster_security_group_ids = var.cluster_security_group_ids + endpoint_private_access = true + endpoint_public_access = var.endpoint_public_access + public_access_cidrs = var.public_access_cidrs - enabled_cluster_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler"] - cloudwatch_log_retention_days = var.cloudwatch_log_retention_days - service_ipv4_cidr = var.service_ipv4_cidr + access_config = var.access_config + deletion_protection = var.deletion_protection + cluster_encryption_config = var.cluster_encryption_config + + enabled_cluster_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler"] + cloudwatch_log_retention_days = var.cloudwatch_log_retention_days + cloudwatch_log_group_kms_key_id = var.cloudwatch_log_group_kms_key_id + service_ipv4_cidr = var.service_ipv4_cidr node_groups = { system = { @@ -38,6 +44,7 @@ module "eks" { labels = { workload = "system" } max_size = 4 min_size = 2 + node_repair_config = var.node_repair_config subnet_ids = local.node_group_subnet_ids update_max_unavailable = 1 } @@ -57,6 +64,7 @@ module "eks" { } max_size = 6 min_size = 0 + node_repair_config = var.node_repair_config subnet_ids = local.node_group_subnet_ids update_max_unavailable = 1 taints = [ diff --git a/examples/advanced/variables.tf b/examples/advanced/variables.tf index 98c5d12..cec34cf 100644 --- a/examples/advanced/variables.tf +++ b/examples/advanced/variables.tf @@ -27,6 +27,12 @@ variable "control_plane_subnet_ids" { type = list(string) } +variable "cluster_security_group_ids" { + description = "Additional security group IDs to associate with the EKS control plane." + type = list(string) + default = [] +} + variable "node_group_subnet_ids" { description = "Optional subnet IDs for managed node groups. Defaults to control_plane_subnet_ids." type = list(string) @@ -45,18 +51,71 @@ variable "public_access_cidrs" { default = ["203.0.113.10/32"] } +variable "access_config" { + description = "Optional EKS access configuration for cluster authentication mode and creator admin permissions." + type = object({ + authentication_mode = optional(string) + bootstrap_cluster_creator_admin_permissions = optional(bool) + }) + default = { + authentication_mode = "API_AND_CONFIG_MAP" + bootstrap_cluster_creator_admin_permissions = true + } +} + +variable "deletion_protection" { + description = "Whether to enable deletion protection for the EKS cluster." + type = bool + default = true +} + +variable "cluster_encryption_config" { + description = "Optional EKS encryption configuration for Kubernetes secrets using an existing KMS key." + type = object({ + provider_key_arn = string + resources = optional(list(string), ["secrets"]) + }) + default = null +} + variable "cloudwatch_log_retention_days" { description = "Retention in days for EKS control plane logs." type = number default = 90 } +variable "cloudwatch_log_group_kms_key_id" { + description = "Optional KMS key ID or ARN for encrypting the EKS control plane CloudWatch log group." + type = string + default = null +} + variable "service_ipv4_cidr" { description = "Optional Kubernetes service IPv4 CIDR." type = string default = null } +variable "node_repair_config" { + description = "Optional node repair configuration applied to both example managed node groups." + type = object({ + enabled = optional(bool) + max_parallel_nodes_repaired_count = optional(number) + max_parallel_nodes_repaired_percentage = optional(number) + max_unhealthy_node_threshold_count = optional(number) + max_unhealthy_node_threshold_percentage = optional(number) + overrides = optional(list(object({ + min_repair_wait_time_mins = number + node_monitoring_condition = string + node_unhealthy_reason = string + repair_action = string + })), []) + }) + default = { + enabled = true + } +} + variable "ebs_csi_driver_service_account_role_arn" { description = "Optional IAM role ARN for the aws-ebs-csi-driver add-on. When null, the add-on is not installed by this example." type = string diff --git a/logs.tf b/logs.tf index 8482a56..790a071 100644 --- a/logs.tf +++ b/logs.tf @@ -2,6 +2,7 @@ resource "aws_cloudwatch_log_group" "cluster" { count = length(var.enabled_cluster_log_types) > 0 ? 1 : 0 name = "/aws/eks/${local.cluster_name}/cluster" + kms_key_id = var.cloudwatch_log_group_kms_key_id retention_in_days = var.cloudwatch_log_retention_days tags = local.common_tags } diff --git a/main.tf b/main.tf index 99c3ef1..006bc65 100644 --- a/main.tf +++ b/main.tf @@ -1,7 +1,8 @@ resource "aws_eks_cluster" "this" { - name = local.cluster_name - role_arn = aws_iam_role.cluster.arn - version = var.kubernetes_version + name = local.cluster_name + role_arn = aws_iam_role.cluster.arn + version = var.kubernetes_version + deletion_protection = var.deletion_protection enabled_cluster_log_types = var.enabled_cluster_log_types @@ -10,6 +11,28 @@ resource "aws_eks_cluster" "this" { endpoint_private_access = var.endpoint_private_access endpoint_public_access = var.endpoint_public_access public_access_cidrs = var.public_access_cidrs + security_group_ids = var.cluster_security_group_ids + } + + dynamic "access_config" { + for_each = var.access_config == null ? [] : [var.access_config] + + content { + authentication_mode = access_config.value.authentication_mode + bootstrap_cluster_creator_admin_permissions = access_config.value.bootstrap_cluster_creator_admin_permissions + } + } + + dynamic "encryption_config" { + for_each = var.cluster_encryption_config == null ? [] : [var.cluster_encryption_config] + + content { + resources = encryption_config.value.resources + + provider { + key_arn = encryption_config.value.provider_key_arn + } + } } dynamic "kubernetes_network_config" { @@ -53,6 +76,29 @@ resource "aws_eks_node_group" "this" { max_unavailable = each.value.update_max_unavailable } + dynamic "node_repair_config" { + for_each = each.value.node_repair_config == null ? [] : [each.value.node_repair_config] + + content { + enabled = node_repair_config.value.enabled + max_parallel_nodes_repaired_count = node_repair_config.value.max_parallel_nodes_repaired_count + max_parallel_nodes_repaired_percentage = node_repair_config.value.max_parallel_nodes_repaired_percentage + max_unhealthy_node_threshold_count = node_repair_config.value.max_unhealthy_node_threshold_count + max_unhealthy_node_threshold_percentage = node_repair_config.value.max_unhealthy_node_threshold_percentage + + dynamic "node_repair_config_overrides" { + for_each = node_repair_config.value.overrides + + content { + min_repair_wait_time_mins = node_repair_config_overrides.value.min_repair_wait_time_mins + node_monitoring_condition = node_repair_config_overrides.value.node_monitoring_condition + node_unhealthy_reason = node_repair_config_overrides.value.node_unhealthy_reason + repair_action = node_repair_config_overrides.value.repair_action + } + } + } + } + dynamic "taint" { for_each = each.value.taints @@ -87,6 +133,15 @@ resource "aws_eks_addon" "this" { service_account_role_arn = each.value.service_account_role_arn tags = local.common_tags + dynamic "pod_identity_association" { + for_each = each.value.pod_identity_associations + + content { + role_arn = pod_identity_association.value.role_arn + service_account = pod_identity_association.value.service_account + } + } + depends_on = [ aws_eks_node_group.this ] diff --git a/tests/customization.tftest.hcl b/tests/customization.tftest.hcl index aa14448..6f1aa0f 100644 --- a/tests/customization.tftest.hcl +++ b/tests/customization.tftest.hcl @@ -36,10 +36,25 @@ run "custom_cluster_shape" { endpoint_private_access = true endpoint_public_access = false public_access_cidrs = ["203.0.113.10/32"] + cluster_security_group_ids = [ + "sg-0123456789abcdef0" + ] + + access_config = { + authentication_mode = "API_AND_CONFIG_MAP" + bootstrap_cluster_creator_admin_permissions = true + } - enabled_cluster_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler"] - cloudwatch_log_retention_days = 90 - service_ipv4_cidr = "172.20.0.0/16" + deletion_protection = true + + enabled_cluster_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler"] + cloudwatch_log_retention_days = 90 + cloudwatch_log_group_kms_key_id = "arn:aws:kms:eu-west-2:123456789012:key/00000000-0000-0000-0000-000000000001" + cluster_encryption_config = { + provider_key_arn = "arn:aws:kms:eu-west-2:123456789012:key/00000000-0000-0000-0000-000000000002" + resources = ["secrets"] + } + service_ipv4_cidr = "172.20.0.0/16" node_groups = { system = { @@ -68,6 +83,19 @@ run "custom_cluster_shape" { max_size = 6 min_size = 0 update_max_unavailable = 1 + node_repair_config = { + enabled = true + max_parallel_nodes_repaired_count = 1 + max_unhealthy_node_threshold_count = 2 + overrides = [ + { + min_repair_wait_time_mins = 30 + node_monitoring_condition = "Ready" + node_unhealthy_reason = "KubeletNotReady" + repair_action = "Replace" + } + ] + } taints = [ { effect = "NO_SCHEDULE" @@ -91,7 +119,12 @@ run "custom_cluster_shape" { } eks-pod-identity-agent = {} aws-ebs-csi-driver = { - service_account_role_arn = "arn:aws:iam::123456789012:role/ebs-csi-driver" + pod_identity_associations = [ + { + role_arn = "arn:aws:iam::123456789012:role/ebs-csi-driver" + service_account = "ebs-csi-controller-sa" + } + ] } } @@ -121,11 +154,31 @@ run "custom_cluster_shape" { error_message = "The cluster should use the requested Kubernetes version." } + assert { + condition = aws_eks_cluster.this.deletion_protection == true + error_message = "Cluster deletion protection should be configurable." + } + + assert { + condition = aws_eks_cluster.this.access_config[0].authentication_mode == "API_AND_CONFIG_MAP" + error_message = "Cluster access authentication mode should be configurable." + } + + assert { + condition = aws_eks_cluster.this.access_config[0].bootstrap_cluster_creator_admin_permissions == true + error_message = "Cluster creator admin bootstrap permissions should be configurable." + } + assert { condition = aws_eks_cluster.this.vpc_config[0].endpoint_public_access == false error_message = "Public endpoint access should be configurable." } + assert { + condition = contains(aws_eks_cluster.this.vpc_config[0].security_group_ids, "sg-0123456789abcdef0") + error_message = "Additional cluster security groups should be configurable." + } + assert { condition = contains(aws_eks_cluster.this.vpc_config[0].public_access_cidrs, "203.0.113.10/32") error_message = "The cluster should use the configured public API CIDR." @@ -136,11 +189,26 @@ run "custom_cluster_shape" { error_message = "The cluster should use the configured service IPv4 CIDR." } + assert { + condition = contains(aws_eks_cluster.this.encryption_config[0].resources, "secrets") + error_message = "Cluster secrets encryption resources should be configurable." + } + + assert { + condition = aws_eks_cluster.this.encryption_config[0].provider[0].key_arn == "arn:aws:kms:eu-west-2:123456789012:key/00000000-0000-0000-0000-000000000002" + error_message = "Cluster secrets encryption should use the configured KMS key." + } + assert { condition = aws_cloudwatch_log_group.cluster[0].retention_in_days == 90 error_message = "Control plane log retention should be configurable." } + assert { + condition = aws_cloudwatch_log_group.cluster[0].kms_key_id == "arn:aws:kms:eu-west-2:123456789012:key/00000000-0000-0000-0000-000000000001" + error_message = "Control plane log group KMS encryption should be configurable." + } + assert { condition = toset(keys(aws_eks_node_group.this)) == toset(["system", "spot"]) error_message = "The module should create the configured managed node groups." @@ -156,13 +224,23 @@ run "custom_cluster_shape" { error_message = "The spot node group should include the configured taint." } + assert { + condition = aws_eks_node_group.this["spot"].node_repair_config[0].enabled == true + error_message = "Node repair config should be configurable for managed node groups." + } + + assert { + condition = one(aws_eks_node_group.this["spot"].node_repair_config[0].node_repair_config_overrides).repair_action == "Replace" + error_message = "Node repair overrides should be configurable for managed node groups." + } + assert { condition = toset(keys(aws_eks_addon.this)) == toset(["aws-ebs-csi-driver", "coredns", "eks-pod-identity-agent", "kube-proxy", "vpc-cni"]) error_message = "The module should create the configured add-on set." } assert { - condition = aws_eks_addon.this["aws-ebs-csi-driver"].service_account_role_arn == "arn:aws:iam::123456789012:role/ebs-csi-driver" - error_message = "IAM-backed add-ons should pass through the configured service account role ARN." + condition = one(aws_eks_addon.this["aws-ebs-csi-driver"].pod_identity_association).service_account == "ebs-csi-controller-sa" + error_message = "Add-ons should support EKS pod identity associations." } } diff --git a/tests/validation.tftest.hcl b/tests/validation.tftest.hcl index 4b7ac24..e785610 100644 --- a/tests/validation.tftest.hcl +++ b/tests/validation.tftest.hcl @@ -57,3 +57,44 @@ run "rejects_invalid_node_group_size_bounds" { var.node_groups ] } + +run "rejects_invalid_access_authentication_mode" { + command = plan + + variables { + name = "unit-invalid-access" + subnet_ids = [ + "subnet-0123456789abcdef0", + "subnet-0fedcba9876543210" + ] + + access_config = { + authentication_mode = "INVALID" + } + } + + expect_failures = [ + var.access_config + ] +} + +run "rejects_unsupported_cluster_encryption_resource" { + command = plan + + variables { + name = "unit-invalid-encryption" + subnet_ids = [ + "subnet-0123456789abcdef0", + "subnet-0fedcba9876543210" + ] + + cluster_encryption_config = { + provider_key_arn = "arn:aws:kms:eu-west-2:123456789012:key/00000000-0000-0000-0000-000000000002" + resources = ["configmaps"] + } + } + + expect_failures = [ + var.cluster_encryption_config + ] +} diff --git a/variables.tf b/variables.tf index aec300b..d033c05 100644 --- a/variables.tf +++ b/variables.tf @@ -35,6 +35,12 @@ variable "subnet_ids" { } } +variable "cluster_security_group_ids" { + description = "Additional security group IDs to associate with the EKS control plane." + type = list(string) + default = [] +} + variable "endpoint_private_access" { description = "Whether the Kubernetes API server endpoint is reachable from within the VPC." type = bool @@ -53,6 +59,50 @@ variable "public_access_cidrs" { default = ["0.0.0.0/0"] } +variable "access_config" { + description = "Optional EKS access configuration for cluster authentication mode and creator admin permissions." + type = object({ + authentication_mode = optional(string) + bootstrap_cluster_creator_admin_permissions = optional(bool) + }) + default = null + + validation { + condition = ( + var.access_config == null || + var.access_config.authentication_mode == null || + contains(["API", "API_AND_CONFIG_MAP", "CONFIG_MAP"], var.access_config.authentication_mode) + ) + error_message = "access_config.authentication_mode must be API, API_AND_CONFIG_MAP, or CONFIG_MAP." + } +} + +variable "deletion_protection" { + description = "Whether to enable deletion protection for the EKS cluster. Leave null to use the AWS/provider default." + type = bool + default = null +} + +variable "cluster_encryption_config" { + description = "Optional EKS encryption configuration for Kubernetes secrets using an existing KMS key." + type = object({ + provider_key_arn = string + resources = optional(list(string), ["secrets"]) + }) + default = null + + validation { + condition = ( + var.cluster_encryption_config == null || + alltrue([ + for resource in var.cluster_encryption_config.resources : + resource == "secrets" + ]) + ) + error_message = "cluster_encryption_config.resources currently supports only secrets." + } +} + variable "enabled_cluster_log_types" { description = "EKS control plane log types to enable." type = list(string) @@ -81,6 +131,12 @@ variable "cloudwatch_log_retention_days" { } } +variable "cloudwatch_log_group_kms_key_id" { + description = "Optional KMS key ID or ARN for encrypting the EKS control plane CloudWatch log group." + type = string + default = null +} + variable "service_ipv4_cidr" { description = "Optional Kubernetes service IPv4 CIDR. Set only when you need a non-default service CIDR." type = string @@ -100,6 +156,19 @@ variable "node_groups" { min_size = optional(number, 1) subnet_ids = optional(list(string), []) update_max_unavailable = optional(number, 1) + node_repair_config = optional(object({ + enabled = optional(bool) + max_parallel_nodes_repaired_count = optional(number) + max_parallel_nodes_repaired_percentage = optional(number) + max_unhealthy_node_threshold_count = optional(number) + max_unhealthy_node_threshold_percentage = optional(number) + overrides = optional(list(object({ + min_repair_wait_time_mins = number + node_monitoring_condition = string + node_unhealthy_reason = string + repair_action = string + })), []) + })) taints = optional(list(object({ effect = string key = string @@ -148,7 +217,11 @@ variable "node_groups" { variable "addons" { description = "EKS add-ons to install after the managed node groups are created." type = map(object({ - configuration_values = optional(string) + configuration_values = optional(string) + pod_identity_associations = optional(list(object({ + role_arn = string + service_account = string + })), []) resolve_conflicts_on_create = optional(string, "OVERWRITE") resolve_conflicts_on_update = optional(string, "OVERWRITE") service_account_role_arn = optional(string) From 0d148b23fdae7b5cbd5d80cea6eb0314319ce148 Mon Sep 17 00:00:00 2001 From: Marcin Cuber Date: Mon, 15 Jun 2026 15:07:38 +0100 Subject: [PATCH 2/2] makefile --- AGENTS.md | 6 ++++++ Makefile | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 10 +++++++++ 3 files changed, 77 insertions(+) create mode 100644 Makefile diff --git a/AGENTS.md b/AGENTS.md index 7928fb9..202dab9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -53,6 +53,12 @@ terraform validate terraform test ``` +You can also run the local aggregate target: + +```bash +make check +``` + For example changes, also run the same init and validate commands from the touched example directory. Do not run `terraform apply`, `terraform destroy`, or destructive state commands unless the user explicitly asks for them and confirms the target environment. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..af6fefb --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +SHELL := /bin/bash + +EXAMPLES := $(wildcard examples/*) + +.PHONY: help fmt fmt-check docs docs-check init validate test examples-init examples-validate check hooks + +help: + @echo "Available targets:" + @echo " make fmt Format all Terraform files" + @echo " make fmt-check Check Terraform formatting" + @echo " make docs Generate README Terraform docs" + @echo " make docs-check Check README Terraform docs are current" + @echo " make init Initialize the root module" + @echo " make validate Validate the root module" + @echo " make test Run native Terraform tests" + @echo " make examples-init Initialize all examples" + @echo " make examples-validate Validate all examples" + @echo " make check Run local CI-style checks" + @echo " make hooks Enable repo-local Git hooks" + +fmt: + terraform fmt -recursive + +fmt-check: + terraform fmt -check -recursive + +docs: + scripts/terraform-docs.sh + +docs-check: + scripts/terraform-docs.sh --check + +init: + terraform init -backend=false + +validate: + terraform validate + +test: + terraform test -no-color + +examples-init: + @for example in $(EXAMPLES); do \ + if [ -d "$$example" ]; then \ + echo "Initializing $$example"; \ + terraform -chdir="$$example" init -backend=false; \ + fi; \ + done + +examples-validate: + @for example in $(EXAMPLES); do \ + if [ -d "$$example" ]; then \ + echo "Validating $$example"; \ + terraform -chdir="$$example" validate; \ + fi; \ + done + +check: fmt-check docs-check init validate test examples-init examples-validate + +hooks: + git config core.hooksPath .githooks diff --git a/README.md b/README.md index dff27a5..1aa5ea9 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,16 @@ terraform test Pull requests run the same tests through `.github/workflows/terraform-pr.yml`, along with formatting, generated-docs, validation, and example checks. +## Local Development + +Use the Makefile for common local checks: + +```bash +make fmt +make docs +make check +``` + ## Notes - The module does not create VPC, subnet, route table, NAT gateway, or security baseline resources.