istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/inject/template.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package inject 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "os" 21 "path" 22 "strconv" 23 "strings" 24 "text/template" 25 26 "google.golang.org/protobuf/proto" 27 "google.golang.org/protobuf/types/known/durationpb" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "sigs.k8s.io/yaml" 31 32 meshconfig "istio.io/api/mesh/v1alpha1" 33 "istio.io/istio/pkg/config/mesh" 34 "istio.io/istio/pkg/log" 35 "istio.io/istio/pkg/util/protomarshal" 36 ) 37 38 var InjectionFuncmap = createInjectionFuncmap() 39 40 func createInjectionFuncmap() template.FuncMap { 41 return template.FuncMap{ 42 "formatDuration": formatDuration, 43 "isset": isset, 44 "excludeInboundPort": excludeInboundPort, 45 "includeInboundPorts": includeInboundPorts, 46 "kubevirtInterfaces": kubevirtInterfaces, 47 "excludeInterfaces": excludeInterfaces, 48 "applicationPorts": applicationPorts, 49 "annotation": getAnnotation, 50 "valueOrDefault": valueOrDefault, 51 "toJSON": toJSON, 52 "fromJSON": fromJSON, 53 "structToJSON": structToJSON, 54 "protoToJSON": protoToJSON, 55 "toYaml": toYaml, 56 "indent": indent, 57 "directory": directory, 58 "contains": flippedContains, 59 "toLower": strings.ToLower, 60 "appendMultusNetwork": appendMultusNetwork, 61 "env": env, 62 "omit": omit, 63 "strdict": strdict, 64 "toJsonMap": toJSONMap, 65 "mergeMaps": mergeMaps, 66 } 67 } 68 69 // Allows the template to use env variables from istiod. 70 // Istiod will use a custom template, without 'values.yaml', and the pod will have 71 // an optional 'vendor' configmap where additional settings can be defined. 72 func env(key string, def string) string { 73 val := os.Getenv(key) 74 if val == "" { 75 return def 76 } 77 return val 78 } 79 80 func formatDuration(in *durationpb.Duration) string { 81 return in.AsDuration().String() 82 } 83 84 func isset(m map[string]string, key string) bool { 85 _, ok := m[key] 86 return ok 87 } 88 89 func directory(filepath string) string { 90 dir, _ := path.Split(filepath) 91 return dir 92 } 93 94 func flippedContains(needle, haystack string) bool { 95 return strings.Contains(haystack, needle) 96 } 97 98 func excludeInboundPort(port any, excludedInboundPorts string) string { 99 portStr := strings.TrimSpace(fmt.Sprint(port)) 100 if len(portStr) == 0 || portStr == "0" { 101 // Nothing to do. 102 return excludedInboundPorts 103 } 104 105 // Exclude the readiness port if not already excluded. 106 ports := splitPorts(excludedInboundPorts) 107 outPorts := make([]string, 0, len(ports)) 108 for _, port := range ports { 109 if port == portStr { 110 // The port is already excluded. 111 return excludedInboundPorts 112 } 113 port = strings.TrimSpace(port) 114 if len(port) > 0 { 115 outPorts = append(outPorts, port) 116 } 117 } 118 119 // The port was not already excluded - exclude it now. 120 outPorts = append(outPorts, portStr) 121 return strings.Join(outPorts, ",") 122 } 123 124 func valueOrDefault(value any, defaultValue any) any { 125 if value == "" || value == nil { 126 return defaultValue 127 } 128 return value 129 } 130 131 func toJSON(m map[string]string) string { 132 if m == nil { 133 return "{}" 134 } 135 136 ba, err := json.Marshal(m) 137 if err != nil { 138 log.Warnf("Unable to marshal %v", m) 139 return "{}" 140 } 141 142 return string(ba) 143 } 144 145 func fromJSON(j string) any { 146 var m any 147 err := json.Unmarshal([]byte(j), &m) 148 if err != nil { 149 log.Warnf("Unable to unmarshal %s", j) 150 return "{}" 151 } 152 153 return m 154 } 155 156 func indent(spaces int, source string) string { 157 res := strings.Split(source, "\n") 158 for i, line := range res { 159 if i > 0 { 160 res[i] = fmt.Sprintf(fmt.Sprintf("%% %ds%%s", spaces), "", line) 161 } 162 } 163 return strings.Join(res, "\n") 164 } 165 166 func toYaml(value any) string { 167 y, err := yaml.Marshal(value) 168 if err != nil { 169 log.Warnf("Unable to marshal %v", value) 170 return "" 171 } 172 173 return string(y) 174 } 175 176 func getAnnotation(meta metav1.ObjectMeta, name string, defaultValue any) string { 177 value, ok := meta.Annotations[name] 178 if !ok { 179 value = fmt.Sprint(defaultValue) 180 } 181 return value 182 } 183 184 func appendMultusNetwork(existingValue, istioCniNetwork string) string { 185 if existingValue == "" { 186 return istioCniNetwork 187 } 188 i := strings.LastIndex(existingValue, "]") 189 isJSON := i != -1 190 if isJSON { 191 networks := []map[string]any{} 192 err := json.Unmarshal([]byte(existingValue), &networks) 193 if err != nil { 194 // existingValue is not valid JSON; nothing we can do but skip injection 195 log.Warnf("Unable to unmarshal Multus Network annotation JSON value: %v", err) 196 return existingValue 197 } 198 for _, net := range networks { 199 if net["name"] == istioCniNetwork { 200 return existingValue 201 } 202 } 203 return existingValue[0:i] + fmt.Sprintf(`, {"name": "%s"}`, istioCniNetwork) + existingValue[i:] 204 } 205 for _, net := range strings.Split(existingValue, ",") { 206 if strings.TrimSpace(net) == istioCniNetwork { 207 return existingValue 208 } 209 } 210 return existingValue + ", " + istioCniNetwork 211 } 212 213 // this function is no longer used by the template but kept around for backwards compatibility 214 func applicationPorts(containers []corev1.Container) string { 215 return getContainerPorts(containers, func(c corev1.Container) bool { 216 return c.Name != ProxyContainerName 217 }) 218 } 219 220 func includeInboundPorts(containers []corev1.Container) string { 221 // Include the ports from all containers in the deployment. 222 return getContainerPorts(containers, func(corev1.Container) bool { return true }) 223 } 224 225 func getPortsForContainer(container corev1.Container) []string { 226 parts := make([]string, 0) 227 for _, p := range container.Ports { 228 if p.Protocol == corev1.ProtocolUDP || p.Protocol == corev1.ProtocolSCTP { 229 continue 230 } 231 parts = append(parts, strconv.Itoa(int(p.ContainerPort))) 232 } 233 return parts 234 } 235 236 func getContainerPorts(containers []corev1.Container, shouldIncludePorts func(corev1.Container) bool) string { 237 parts := make([]string, 0) 238 for _, c := range containers { 239 if shouldIncludePorts(c) { 240 parts = append(parts, getPortsForContainer(c)...) 241 } 242 } 243 244 return strings.Join(parts, ",") 245 } 246 247 func kubevirtInterfaces(s string) string { 248 return s 249 } 250 251 func excludeInterfaces(s string) string { 252 return s 253 } 254 255 func structToJSON(v any) string { 256 if v == nil { 257 return "{}" 258 } 259 260 ba, err := json.Marshal(v) 261 if err != nil { 262 log.Warnf("Unable to marshal %v", v) 263 return "{}" 264 } 265 266 return string(ba) 267 } 268 269 func protoToJSON(v proto.Message) string { 270 v = cleanProxyConfig(v) 271 if v == nil { 272 return "{}" 273 } 274 275 ba, err := protomarshal.ToJSON(v) 276 if err != nil { 277 log.Warnf("Unable to marshal %v: %v", v, err) 278 return "{}" 279 } 280 281 return ba 282 } 283 284 // Rather than dump the entire proxy config, we remove fields that are default 285 // This makes the pod spec much smaller 286 // This is not comprehensive code, but nothing will break if this misses some fields 287 func cleanProxyConfig(msg proto.Message) proto.Message { 288 originalProxyConfig, ok := msg.(*meshconfig.ProxyConfig) 289 if !ok || originalProxyConfig == nil { 290 return msg 291 } 292 pc := proto.Clone(originalProxyConfig).(*meshconfig.ProxyConfig) 293 defaults := mesh.DefaultProxyConfig() 294 if pc.ConfigPath == defaults.ConfigPath { 295 pc.ConfigPath = "" 296 } 297 if pc.BinaryPath == defaults.BinaryPath { 298 pc.BinaryPath = "" 299 } 300 if pc.ControlPlaneAuthPolicy == defaults.ControlPlaneAuthPolicy { 301 pc.ControlPlaneAuthPolicy = 0 302 } 303 if x, ok := pc.GetClusterName().(*meshconfig.ProxyConfig_ServiceCluster); ok { 304 if x.ServiceCluster == defaults.GetClusterName().(*meshconfig.ProxyConfig_ServiceCluster).ServiceCluster { 305 pc.ClusterName = nil 306 } 307 } 308 309 if proto.Equal(pc.DrainDuration, defaults.DrainDuration) { 310 pc.DrainDuration = nil 311 } 312 if proto.Equal(pc.TerminationDrainDuration, defaults.TerminationDrainDuration) { 313 pc.TerminationDrainDuration = nil 314 } 315 if pc.DiscoveryAddress == defaults.DiscoveryAddress { 316 pc.DiscoveryAddress = "" 317 } 318 if proto.Equal(pc.EnvoyMetricsService, defaults.EnvoyMetricsService) { 319 pc.EnvoyMetricsService = nil 320 } 321 if proto.Equal(pc.EnvoyAccessLogService, defaults.EnvoyAccessLogService) { 322 pc.EnvoyAccessLogService = nil 323 } 324 if proto.Equal(pc.Tracing, defaults.Tracing) { 325 pc.Tracing = nil 326 } 327 if pc.ProxyAdminPort == defaults.ProxyAdminPort { 328 pc.ProxyAdminPort = 0 329 } 330 if pc.StatNameLength == defaults.StatNameLength { 331 pc.StatNameLength = 0 332 } 333 if pc.StatusPort == defaults.StatusPort { 334 pc.StatusPort = 0 335 } 336 if proto.Equal(pc.Concurrency, defaults.Concurrency) { 337 pc.Concurrency = nil 338 } 339 if len(pc.ProxyMetadata) == 0 { 340 pc.ProxyMetadata = nil 341 } 342 return proto.Message(pc) 343 } 344 345 func toJSONMap(mps ...map[string]string) string { 346 data, err := json.Marshal(mergeMaps(mps...)) 347 if err != nil { 348 return "" 349 } 350 return string(data) 351 } 352 353 func omit(dict map[string]string, keys ...string) map[string]string { 354 res := map[string]string{} 355 356 omit := make(map[string]bool, len(keys)) 357 for _, k := range keys { 358 omit[k] = true 359 } 360 361 for k, v := range dict { 362 if _, ok := omit[k]; !ok { 363 res[k] = v 364 } 365 } 366 return res 367 } 368 369 // strdict is the same as the "dict" function (http://masterminds.github.io/sprig/dicts.html) 370 // but returns a map[string]string instead of interface{} types. This allows it to be used 371 // in annotations/labels. 372 func strdict(v ...string) map[string]string { 373 dict := map[string]string{} 374 lenv := len(v) 375 for i := 0; i < lenv; i += 2 { 376 key := v[i] 377 if i+1 >= lenv { 378 dict[key] = "" 379 continue 380 } 381 dict[key] = v[i+1] 382 } 383 return dict 384 } 385 386 // Merge maps merges multiple maps. Latter maps take precedence over previous maps on overlapping fields 387 func mergeMaps(maps ...map[string]string) map[string]string { 388 if len(maps) == 0 { 389 return nil 390 } 391 res := make(map[string]string, len(maps[0])) 392 for _, m := range maps { 393 for k, v := range m { 394 res[k] = v 395 } 396 } 397 return res 398 }