|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +# Copyright 2025 The Kubernetes Authors. |
| 4 | +# |
| 5 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | +# you may not use this file except in compliance with the License. |
| 7 | +# You may obtain a copy of the License at |
| 8 | +# |
| 9 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +# |
| 11 | +# Unless required by applicable law or agreed to in writing, software |
| 12 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | +# See the License for the specific language governing permissions and |
| 15 | +# limitations under the License. |
| 16 | + |
| 17 | +# This script syncs RBAC rules from the kubebuilder-generated role.yaml |
| 18 | +# into the helm chart's rbac.yaml template, keeping helm-specific sections |
| 19 | +# (leader election, bindings, conditionals) intact. |
| 20 | + |
| 21 | +set -o errexit |
| 22 | +set -o nounset |
| 23 | +set -o pipefail |
| 24 | + |
| 25 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 26 | +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" |
| 27 | + |
| 28 | +KUBEBUILDER_ROLE="${ROOT_DIR}/config/rbac/role.yaml" |
| 29 | +HELM_RBAC="${ROOT_DIR}/helm/aws-load-balancer-controller/templates/rbac.yaml" |
| 30 | + |
| 31 | +if [ ! -f "${KUBEBUILDER_ROLE}" ]; then |
| 32 | + echo "Error: kubebuilder role not found at ${KUBEBUILDER_ROLE}" |
| 33 | + echo "Run 'make manifests' first." |
| 34 | + exit 1 |
| 35 | +fi |
| 36 | + |
| 37 | +if [ ! -f "${HELM_RBAC}" ]; then |
| 38 | + echo "Error: helm rbac template not found at ${HELM_RBAC}" |
| 39 | + exit 1 |
| 40 | +fi |
| 41 | + |
| 42 | +# Extract rules from kubebuilder role.yaml and convert to helm flow style |
| 43 | +# We use python3 to parse the kubebuilder YAML (simple structure, no external deps) |
| 44 | +generate_helm_clusterrole_rules() { |
| 45 | + python3 -c ' |
| 46 | +import sys |
| 47 | +
|
| 48 | +def parse_role_yaml(filepath): |
| 49 | + """Parse kubebuilder role.yaml without external YAML library.""" |
| 50 | + with open(filepath) as f: |
| 51 | + lines = f.readlines() |
| 52 | +
|
| 53 | + rules = [] |
| 54 | + current_rule = None |
| 55 | + current_key = None |
| 56 | + in_rules = False |
| 57 | +
|
| 58 | + for line in lines: |
| 59 | + raw = line.rstrip("\n") |
| 60 | + stripped = raw.strip() |
| 61 | +
|
| 62 | + if not stripped or stripped.startswith("#") or stripped.startswith("---"): |
| 63 | + continue |
| 64 | +
|
| 65 | + # Detect top-level keys |
| 66 | + if not raw.startswith(" ") and not raw.startswith("-"): |
| 67 | + if stripped == "rules:": |
| 68 | + in_rules = True |
| 69 | + else: |
| 70 | + in_rules = False |
| 71 | + continue |
| 72 | +
|
| 73 | + if not in_rules: |
| 74 | + continue |
| 75 | +
|
| 76 | + # New rule: line starts with "- " at rule level (typically 0 or minimal indent) |
| 77 | + if raw.startswith("- "): |
| 78 | + if current_rule: |
| 79 | + rules.append(current_rule) |
| 80 | + current_rule = {} |
| 81 | + # Handle "- apiGroups:" on the same line |
| 82 | + key_part = stripped[2:] # remove "- " |
| 83 | + if key_part.endswith(":"): |
| 84 | + current_key = key_part[:-1] |
| 85 | + current_rule[current_key] = [] |
| 86 | + continue |
| 87 | +
|
| 88 | + # Key at rule level (indented, no dash) |
| 89 | + if not stripped.startswith("-") and stripped.endswith(":"): |
| 90 | + current_key = stripped[:-1].strip() |
| 91 | + if current_rule is not None and current_key not in current_rule: |
| 92 | + current_rule[current_key] = [] |
| 93 | + continue |
| 94 | +
|
| 95 | + # List item |
| 96 | + if stripped.startswith("- "): |
| 97 | + value = stripped[2:].strip().strip("\"").strip("'\''") |
| 98 | + if current_rule is not None and current_key: |
| 99 | + current_rule[current_key].append(value) |
| 100 | + continue |
| 101 | +
|
| 102 | + if current_rule: |
| 103 | + rules.append(current_rule) |
| 104 | +
|
| 105 | + return rules |
| 106 | +
|
| 107 | +def format_list(items): |
| 108 | + return "[" + ", ".join(str(i) for i in items) + "]" |
| 109 | +
|
| 110 | +rules = parse_role_yaml(sys.argv[1]) |
| 111 | +for rule in rules: |
| 112 | + api_groups = rule.get("apiGroups", []) |
| 113 | + resources = rule.get("resources", []) |
| 114 | + verbs = rule.get("verbs", []) |
| 115 | + resource_names = rule.get("resourceNames", None) |
| 116 | +
|
| 117 | + formatted_groups = [] |
| 118 | + for g in api_groups: |
| 119 | + if g == "": |
| 120 | + formatted_groups.append("\"\"") |
| 121 | + else: |
| 122 | + formatted_groups.append("\"" + g + "\"") |
| 123 | +
|
| 124 | + print("- apiGroups: [{}]".format(", ".join(formatted_groups))) |
| 125 | + print(" resources: {}".format(format_list(resources))) |
| 126 | + if resource_names: |
| 127 | + print(" resourceNames: {}".format(format_list(resource_names))) |
| 128 | + print(" verbs: {}".format(format_list(verbs))) |
| 129 | +' "${KUBEBUILDER_ROLE}" |
| 130 | +} |
| 131 | + |
| 132 | +# Build the new helm rbac.yaml |
| 133 | +# We preserve the leader election Role, RoleBinding, and ClusterRoleBinding |
| 134 | +# from the existing template, and only replace the ClusterRole rules. |
| 135 | + |
| 136 | +GENERATED_RULES=$(generate_helm_clusterrole_rules) |
| 137 | + |
| 138 | +cat > "${HELM_RBAC}" << 'HELM_HEADER' |
| 139 | +{{- if .Values.rbac.create }} |
| 140 | +apiVersion: rbac.authorization.k8s.io/v1 |
| 141 | +kind: Role |
| 142 | +metadata: |
| 143 | + name: {{ template "aws-load-balancer-controller.fullname" . }}-leader-election-role |
| 144 | + namespace: {{ .Release.Namespace }} |
| 145 | + labels: |
| 146 | + {{- include "aws-load-balancer-controller.labels" . | nindent 4 }} |
| 147 | +rules: |
| 148 | +- apiGroups: [""] |
| 149 | + resources: [configmaps] |
| 150 | + verbs: [create] |
| 151 | +- apiGroups: [""] |
| 152 | + resources: [configmaps] |
| 153 | + resourceNames: [aws-load-balancer-controller-leader] |
| 154 | + verbs: [get, patch, update] |
| 155 | +- apiGroups: |
| 156 | + - "coordination.k8s.io" |
| 157 | + resources: |
| 158 | + - leases |
| 159 | + verbs: |
| 160 | + - create |
| 161 | +- apiGroups: |
| 162 | + - "coordination.k8s.io" |
| 163 | + resources: |
| 164 | + - leases |
| 165 | + resourceNames: |
| 166 | + - aws-load-balancer-controller-leader |
| 167 | + verbs: |
| 168 | + - get |
| 169 | + - update |
| 170 | + - patch |
| 171 | +--- |
| 172 | +apiVersion: rbac.authorization.k8s.io/v1 |
| 173 | +kind: RoleBinding |
| 174 | +metadata: |
| 175 | + name: {{ template "aws-load-balancer-controller.fullname" . }}-leader-election-rolebinding |
| 176 | + namespace: {{ .Release.Namespace }} |
| 177 | + labels: |
| 178 | + {{- include "aws-load-balancer-controller.labels" . | nindent 4 }} |
| 179 | +roleRef: |
| 180 | + apiGroup: rbac.authorization.k8s.io |
| 181 | + kind: Role |
| 182 | + name: {{ template "aws-load-balancer-controller.fullname" . }}-leader-election-role |
| 183 | +subjects: |
| 184 | +- kind: ServiceAccount |
| 185 | + name: {{ template "aws-load-balancer-controller.serviceAccountName" . }} |
| 186 | + namespace: {{ .Release.Namespace }} |
| 187 | +--- |
| 188 | +apiVersion: rbac.authorization.k8s.io/v1 |
| 189 | +kind: ClusterRole |
| 190 | +metadata: |
| 191 | + name: {{ template "aws-load-balancer-controller.fullname" . }}-role |
| 192 | + labels: |
| 193 | + {{- include "aws-load-balancer-controller.labels" . | nindent 4 }} |
| 194 | +rules: |
| 195 | +HELM_HEADER |
| 196 | + |
| 197 | +# Append the generated rules (from kubebuilder source of truth) |
| 198 | +echo "# AUTO-GENERATED from config/rbac/role.yaml by hack/sync-rbac-to-helm.sh" >> "${HELM_RBAC}" |
| 199 | +echo "# Do not edit these rules manually. Run 'make manifests' to update." >> "${HELM_RBAC}" |
| 200 | +echo "${GENERATED_RULES}" >> "${HELM_RBAC}" |
| 201 | + |
| 202 | +# Append helm-specific conditional rules and the ClusterRoleBinding |
| 203 | +cat >> "${HELM_RBAC}" << 'HELM_FOOTER' |
| 204 | +{{- if .Values.clusterSecretsPermissions.allowAllSecrets }} |
| 205 | +- apiGroups: [""] |
| 206 | + resources: [secrets] |
| 207 | + verbs: [get, list, watch] |
| 208 | +{{- end }} |
| 209 | +--- |
| 210 | +apiVersion: rbac.authorization.k8s.io/v1 |
| 211 | +kind: ClusterRoleBinding |
| 212 | +metadata: |
| 213 | + name: {{ template "aws-load-balancer-controller.fullname" . }}-rolebinding |
| 214 | + labels: |
| 215 | + {{- include "aws-load-balancer-controller.labels" . | nindent 4 }} |
| 216 | +roleRef: |
| 217 | + apiGroup: rbac.authorization.k8s.io |
| 218 | + kind: ClusterRole |
| 219 | + name: {{ template "aws-load-balancer-controller.fullname" . }}-role |
| 220 | +subjects: |
| 221 | +- kind: ServiceAccount |
| 222 | + name: {{ template "aws-load-balancer-controller.serviceAccountName" . }} |
| 223 | + namespace: {{ .Release.Namespace }} |
| 224 | +{{- end }} |
| 225 | +HELM_FOOTER |
| 226 | + |
| 227 | +echo "Synced RBAC rules from ${KUBEBUILDER_ROLE} to ${HELM_RBAC}" |
0 commit comments