Skip to content

Commit 6a47a8c

Browse files
RoguedBearclaude
andauthored
fix: lookup-safe access for optional cluster_addons + additional_node_policies (RULE-015) (#350)
Optional spec fields must be read via lookup(var.instance.spec, "field", default) rather than var.instance.spec.field directly. The Facets exec path does not reliably apply the optional() defaults declared in variables.tf, so a blueprint that omits an optional field crashes at plan/apply on the direct attribute access (RULE-015). Two crash sites fixed: 1. kubernetes_cluster/eks_standard/1.0/main.tf — cluster_addons is optional (only cluster_version is required). The locals read var.instance.spec.cluster_addons and nested .vpc_cni / .kube_proxy / .coredns directly, so a blueprint omitting cluster_addons crashed. Added a cluster_addons_spec = lookup(var.instance.spec, "cluster_addons", {}) local and routed every addon read through nested lookups with defaults. Omitting cluster_addons now enables all default addons (matching the schema/UI defaults). 2. karpenter/default/1.0/main.tf — additional_node_policies is optional but was read directly via values(var.instance.spec.additional_node_policies), crashing when omitted. Now values(lookup(var.instance.spec, "additional_node_policies", {})). Backward-compatible bugfix: behaviour is identical when the fields are present, and previously-crashing omissions now fall back to documented defaults. No version bump (RULE-023 reserves bumps for breaking changes). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5cbb34d commit 6a47a8c

2 files changed

Lines changed: 18 additions & 12 deletions

File tree

  • modules
    • karpenter/default/1.0
    • kubernetes_cluster/eks_standard/1.0

modules/karpenter/default/1.0/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ resource "aws_iam_role_policy_attachment" "karpenter_node_policies" {
197197
"arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy",
198198
"arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly",
199199
"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
200-
], [for policy in values(var.instance.spec.additional_node_policies) : policy.arn]))
200+
], [for policy in values(lookup(var.instance.spec, "additional_node_policies", {})) : policy.arn]))
201201

202202
role = aws_iam_role.karpenter_node.name
203203
policy_arn = each.value

modules/kubernetes_cluster/eks_standard/1.0/main.tf

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,31 +52,37 @@ locals {
5252
# Only the default system node group
5353
eks_managed_node_groups = local.default_system_node_group
5454

55+
# cluster_addons is an OPTIONAL spec field (only cluster_version is required). Default to {}
56+
# so an omitted block doesn't crash on direct attribute access — RULE-015. The per-addon
57+
# `enabled`/`version` defaults below then apply, so omitting cluster_addons enables all
58+
# default addons (matching the schema/UI defaults).
59+
cluster_addons_spec = lookup(var.instance.spec, "cluster_addons", {})
60+
5561
# Check if EBS CSI driver addon is enabled (default: true)
56-
ebs_csi_enabled = lookup(lookup(var.instance.spec.cluster_addons, "ebs_csi", {}), "enabled", true)
62+
ebs_csi_enabled = lookup(lookup(local.cluster_addons_spec, "ebs_csi", {}), "enabled", true)
5763

5864
# Build cluster addons configuration - default addons
5965
default_addons = {
60-
vpc-cni = lookup(var.instance.spec.cluster_addons.vpc_cni, "enabled", true) ? {
61-
addon_version = lookup(var.instance.spec.cluster_addons.vpc_cni, "version", "latest") == "latest" ? null : lookup(var.instance.spec.cluster_addons.vpc_cni, "version", null)
66+
vpc-cni = lookup(lookup(local.cluster_addons_spec, "vpc_cni", {}), "enabled", true) ? {
67+
addon_version = lookup(lookup(local.cluster_addons_spec, "vpc_cni", {}), "version", "latest") == "latest" ? null : lookup(lookup(local.cluster_addons_spec, "vpc_cni", {}), "version", null)
6268
resolve_conflicts = "OVERWRITE"
6369
service_account_role_arn = null
6470
} : null
6571

66-
kube-proxy = lookup(var.instance.spec.cluster_addons.kube_proxy, "enabled", true) ? {
67-
addon_version = lookup(var.instance.spec.cluster_addons.kube_proxy, "version", "latest") == "latest" ? null : lookup(var.instance.spec.cluster_addons.kube_proxy, "version", null)
72+
kube-proxy = lookup(lookup(local.cluster_addons_spec, "kube_proxy", {}), "enabled", true) ? {
73+
addon_version = lookup(lookup(local.cluster_addons_spec, "kube_proxy", {}), "version", "latest") == "latest" ? null : lookup(lookup(local.cluster_addons_spec, "kube_proxy", {}), "version", null)
6874
resolve_conflicts = "OVERWRITE"
6975
service_account_role_arn = null
7076
} : null
7177

72-
coredns = lookup(var.instance.spec.cluster_addons.coredns, "enabled", true) ? {
73-
addon_version = lookup(var.instance.spec.cluster_addons.coredns, "version", "latest") == "latest" ? null : lookup(var.instance.spec.cluster_addons.coredns, "version", null)
78+
coredns = lookup(lookup(local.cluster_addons_spec, "coredns", {}), "enabled", true) ? {
79+
addon_version = lookup(lookup(local.cluster_addons_spec, "coredns", {}), "version", "latest") == "latest" ? null : lookup(lookup(local.cluster_addons_spec, "coredns", {}), "version", null)
7480
resolve_conflicts = "OVERWRITE"
7581
service_account_role_arn = null
7682
} : null
7783

7884
aws-ebs-csi-driver = local.ebs_csi_enabled ? {
79-
addon_version = lookup(lookup(var.instance.spec.cluster_addons, "ebs_csi", {}), "version", "latest") == "latest" ? null : lookup(lookup(var.instance.spec.cluster_addons, "ebs_csi", {}), "version", null)
85+
addon_version = lookup(lookup(local.cluster_addons_spec, "ebs_csi", {}), "version", "latest") == "latest" ? null : lookup(lookup(local.cluster_addons_spec, "ebs_csi", {}), "version", null)
8086
resolve_conflicts = "OVERWRITE"
8187
service_account_role_arn = aws_iam_role.ebs_csi_driver[0].arn
8288
} : null
@@ -87,16 +93,16 @@ locals {
8793
service_account_role_arn = local.needs_cloudwatch_iam_policy ? aws_iam_role.cloudwatch_agent_irsa[0].arn : null
8894
} : null
8995

90-
metrics-server = lookup(lookup(var.instance.spec.cluster_addons, "metrics_server", {}), "enabled", true) ? {
91-
addon_version = lookup(lookup(var.instance.spec.cluster_addons, "metrics_server", {}), "version", "latest") == "latest" ? null : lookup(lookup(var.instance.spec.cluster_addons, "metrics_server", {}), "version", null)
96+
metrics-server = lookup(lookup(local.cluster_addons_spec, "metrics_server", {}), "enabled", true) ? {
97+
addon_version = lookup(lookup(local.cluster_addons_spec, "metrics_server", {}), "version", "latest") == "latest" ? null : lookup(lookup(local.cluster_addons_spec, "metrics_server", {}), "version", null)
9298
resolve_conflicts = "OVERWRITE"
9399
service_account_role_arn = null
94100
} : null
95101
}
96102

97103
# Build additional/custom addons configuration
98104
additional_addons = {
99-
for addon_name, addon_config in lookup(var.instance.spec.cluster_addons, "additional_addons", {}) :
105+
for addon_name, addon_config in lookup(local.cluster_addons_spec, "additional_addons", {}) :
100106
addon_name => lookup(addon_config, "enabled", true) ? {
101107
addon_version = lookup(addon_config, "version", "latest") == "latest" ? null : lookup(addon_config, "version", null)
102108
resolve_conflicts = "OVERWRITE"

0 commit comments

Comments
 (0)