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  }