Skip to content

Commit f8651ab

Browse files
Benbentwoclaude
andcommitted
feat: add EKS Capabilities support (Argo CD, ACK, KRO)
- Add `capabilities` map variable for independently-enableable managed platform features - Create capabilities.tf with aws_eks_capability resources and auto-created IAM roles per capability - Add capabilities and capability_role_arns outputs - Bump AWS provider to >= 6.25.0 for aws_eks_capability resource - Support ARGOCD configuration (IDC, RBAC, network access) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent eda8a0a commit f8651ab

5 files changed

Lines changed: 213 additions & 18 deletions

File tree

.terraform.lock.hcl

Lines changed: 17 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

capabilities.tf

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# EKS Capabilities: Argo CD, ACK, KRO
2+
# https://docs.aws.amazon.com/eks/latest/userguide/capabilities.html
3+
4+
locals {
5+
enabled_capabilities = {
6+
for k, v in var.capabilities : k => v if local.enabled && v.enabled
7+
}
8+
9+
# Capabilities that need auto-created IAM roles
10+
capabilities_needing_roles = {
11+
for k, v in local.enabled_capabilities : k => v if v.role_arn == null
12+
}
13+
14+
# Final role ARN map: auto-created or user-provided
15+
capability_role_arns = {
16+
for k, v in local.enabled_capabilities : k => coalesce(
17+
v.role_arn,
18+
try(aws_iam_role.capability[k].arn, null)
19+
)
20+
}
21+
}
22+
23+
# IAM roles for capabilities that don't provide their own
24+
module "capability_label" {
25+
for_each = local.capabilities_needing_roles
26+
27+
source = "cloudposse/label/null"
28+
version = "0.25.0"
29+
30+
attributes = ["capability", each.key]
31+
context = module.this.context
32+
}
33+
34+
data "aws_iam_policy_document" "capability_assume_role" {
35+
count = length(local.capabilities_needing_roles) > 0 ? 1 : 0
36+
37+
statement {
38+
effect = "Allow"
39+
actions = ["sts:AssumeRole", "sts:TagSession"]
40+
41+
principals {
42+
type = "Service"
43+
identifiers = ["capabilities.eks.amazonaws.com"]
44+
}
45+
}
46+
}
47+
48+
resource "aws_iam_role" "capability" {
49+
for_each = local.capabilities_needing_roles
50+
51+
name = module.capability_label[each.key].id
52+
assume_role_policy = one(data.aws_iam_policy_document.capability_assume_role[*].json)
53+
tags = module.capability_label[each.key].tags
54+
permissions_boundary = var.permissions_boundary
55+
}
56+
57+
resource "aws_eks_capability" "default" {
58+
for_each = local.enabled_capabilities
59+
60+
cluster_name = local.eks_cluster_id
61+
capability_name = each.key
62+
type = each.value.type
63+
role_arn = local.capability_role_arns[each.key]
64+
delete_propagation_policy = each.value.delete_propagation_policy
65+
tags = module.label.tags
66+
67+
dynamic "configuration" {
68+
for_each = each.value.configuration != null && each.value.type == "ARGOCD" ? [each.value.configuration] : []
69+
content {
70+
dynamic "argo_cd" {
71+
for_each = configuration.value.argo_cd != null ? [configuration.value.argo_cd] : []
72+
content {
73+
namespace = argo_cd.value.namespace
74+
75+
dynamic "aws_idc" {
76+
for_each = argo_cd.value.aws_idc != null ? [argo_cd.value.aws_idc] : []
77+
content {
78+
idc_instance_arn = aws_idc.value.idc_instance_arn
79+
idc_region = aws_idc.value.idc_region
80+
}
81+
}
82+
83+
dynamic "network_access" {
84+
for_each = argo_cd.value.network_access != null ? [argo_cd.value.network_access] : []
85+
content {
86+
vpce_ids = network_access.value.vpce_ids
87+
}
88+
}
89+
90+
dynamic "rbac_role_mapping" {
91+
for_each = argo_cd.value.rbac_role_mapping
92+
content {
93+
role = rbac_role_mapping.value.role
94+
95+
dynamic "identity" {
96+
for_each = rbac_role_mapping.value.identity
97+
content {
98+
id = identity.value.id
99+
type = identity.value.type
100+
}
101+
}
102+
}
103+
}
104+
}
105+
}
106+
}
107+
}
108+
109+
timeouts {
110+
create = each.value.create_timeout
111+
update = each.value.update_timeout
112+
delete = each.value.delete_timeout
113+
}
114+
115+
depends_on = [
116+
aws_eks_cluster.default,
117+
aws_iam_role.capability,
118+
]
119+
}

outputs.tf

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,24 @@ output "auto_mode_enabled" {
7575
value = local.auto_mode_all_enabled
7676
}
7777

78+
output "capabilities" {
79+
description = "Map of enabled EKS Capabilities with their ARNs and types"
80+
value = {
81+
for k, v in aws_eks_capability.default : k => {
82+
arn = v.arn
83+
type = v.type
84+
version = v.version
85+
}
86+
}
87+
}
88+
89+
output "capability_role_arns" {
90+
description = "Map of auto-created capability IAM role ARNs"
91+
value = {
92+
for k, v in aws_iam_role.capability : k => v.arn
93+
}
94+
}
95+
7896
output "cluster_encryption_config_enabled" {
7997
description = "If true, Cluster Encryption Configuration is enabled"
8098
value = var.cluster_encryption_config_enabled

variables.tf

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,64 @@ variable "auto_mode_elastic_load_balancing" {
246246
nullable = false
247247
}
248248

249+
variable "capabilities" {
250+
description = <<-EOT
251+
Map of EKS Capabilities to enable on the cluster. Each key is the capability
252+
name (must be unique within the cluster). Supported types: ACK, ARGOCD, KRO.
253+
254+
When `role_arn` is null, an IAM role with a trust policy for
255+
`capabilities.eks.amazonaws.com` is automatically created.
256+
257+
The `configuration` block is only applicable to ARGOCD capabilities.
258+
ACK and KRO do not currently support configuration.
259+
EOT
260+
type = map(object({
261+
enabled = optional(bool, true)
262+
type = string # ACK, ARGOCD, KRO
263+
role_arn = optional(string, null)
264+
delete_propagation_policy = optional(string, "RETAIN")
265+
configuration = optional(object({
266+
argo_cd = optional(object({
267+
namespace = optional(string, "argocd")
268+
aws_idc = optional(object({
269+
idc_instance_arn = string
270+
idc_region = optional(string, null)
271+
}), null)
272+
network_access = optional(object({
273+
vpce_ids = optional(list(string), [])
274+
}), null)
275+
rbac_role_mapping = optional(list(object({
276+
role = string # ADMIN, EDITOR, VIEWER
277+
identity = list(object({
278+
id = string
279+
type = string # SSO_USER, SSO_GROUP
280+
}))
281+
})), [])
282+
}), null)
283+
}), null)
284+
create_timeout = optional(string, null)
285+
update_timeout = optional(string, null)
286+
delete_timeout = optional(string, null)
287+
}))
288+
default = {}
289+
nullable = false
290+
291+
validation {
292+
condition = alltrue([
293+
for k, v in var.capabilities : contains(["ACK", "ARGOCD", "KRO"], v.type)
294+
])
295+
error_message = "Each capability type must be one of: ACK, ARGOCD, KRO."
296+
}
297+
298+
validation {
299+
condition = alltrue([
300+
for k, v in var.capabilities :
301+
v.configuration == null || v.type == "ARGOCD"
302+
])
303+
error_message = "The configuration block is only supported for ARGOCD capabilities."
304+
}
305+
}
306+
249307
variable "upgrade_policy" {
250308
type = object({
251309
support_type = optional(string, null)

versions.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ terraform {
44
required_providers {
55
aws = {
66
source = "hashicorp/aws"
7-
version = ">= 5.79.0"
7+
version = ">= 6.25.0"
88
}
99
tls = {
1010
source = "hashicorp/tls"

0 commit comments

Comments
 (0)