blob: 55fe94f77e2db40b2b3305d5d8f0bd69fc1e7844 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package inject
import (
"encoding/json"
"fmt"
"os"
"path"
"strconv"
"strings"
"text/template"
)
import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
meshconfig "istio.io/api/mesh/v1alpha1"
"istio.io/pkg/log"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/config/mesh"
"github.com/apache/dubbo-go-pixiu/pkg/util/protomarshal"
)
var InjectionFuncmap = createInjectionFuncmap()
func createInjectionFuncmap() template.FuncMap {
return template.FuncMap{
"formatDuration": formatDuration,
"isset": isset,
"excludeInboundPort": excludeInboundPort,
"includeInboundPorts": includeInboundPorts,
"kubevirtInterfaces": kubevirtInterfaces,
"applicationPorts": applicationPorts,
"annotation": getAnnotation,
"valueOrDefault": valueOrDefault,
"toJSON": toJSON,
"toJson": toJSON, // Used by, e.g. Istio 1.0.5 template sidecar-injector-configmap.yaml
"fromJSON": fromJSON,
"structToJSON": structToJSON,
"protoToJSON": protoToJSON,
"toYaml": toYaml,
"indent": indent,
"directory": directory,
"contains": flippedContains,
"toLower": strings.ToLower,
"appendMultusNetwork": appendMultusNetwork,
"env": env,
}
}
// Allows the template to use env variables from istiod.
// Istiod will use a custom template, without 'values.yaml', and the pod will have
// an optional 'vendor' configmap where additional settings can be defined.
func env(key string, def string) string {
val := os.Getenv(key)
if val == "" {
return def
}
return val
}
func formatDuration(in *durationpb.Duration) string {
return in.AsDuration().String()
}
func isset(m map[string]string, key string) bool {
_, ok := m[key]
return ok
}
func directory(filepath string) string {
dir, _ := path.Split(filepath)
return dir
}
func flippedContains(needle, haystack string) bool {
return strings.Contains(haystack, needle)
}
func excludeInboundPort(port interface{}, excludedInboundPorts string) string {
portStr := strings.TrimSpace(fmt.Sprint(port))
if len(portStr) == 0 || portStr == "0" {
// Nothing to do.
return excludedInboundPorts
}
// Exclude the readiness port if not already excluded.
ports := splitPorts(excludedInboundPorts)
outPorts := make([]string, 0, len(ports))
for _, port := range ports {
if port == portStr {
// The port is already excluded.
return excludedInboundPorts
}
port = strings.TrimSpace(port)
if len(port) > 0 {
outPorts = append(outPorts, port)
}
}
// The port was not already excluded - exclude it now.
outPorts = append(outPorts, portStr)
return strings.Join(outPorts, ",")
}
func valueOrDefault(value interface{}, defaultValue interface{}) interface{} {
if value == "" || value == nil {
return defaultValue
}
return value
}
func toJSON(m map[string]string) string {
if m == nil {
return "{}"
}
ba, err := json.Marshal(m)
if err != nil {
log.Warnf("Unable to marshal %v", m)
return "{}"
}
return string(ba)
}
func fromJSON(j string) interface{} {
var m interface{}
err := json.Unmarshal([]byte(j), &m)
if err != nil {
log.Warnf("Unable to unmarshal %s", j)
return "{}"
}
return m
}
func indent(spaces int, source string) string {
res := strings.Split(source, "\n")
for i, line := range res {
if i > 0 {
res[i] = fmt.Sprintf(fmt.Sprintf("%% %ds%%s", spaces), "", line)
}
}
return strings.Join(res, "\n")
}
func toYaml(value interface{}) string {
y, err := yaml.Marshal(value)
if err != nil {
log.Warnf("Unable to marshal %v", value)
return ""
}
return string(y)
}
func getAnnotation(meta metav1.ObjectMeta, name string, defaultValue interface{}) string {
value, ok := meta.Annotations[name]
if !ok {
value = fmt.Sprint(defaultValue)
}
return value
}
func appendMultusNetwork(existingValue, istioCniNetwork string) string {
if existingValue == "" {
return istioCniNetwork
}
i := strings.LastIndex(existingValue, "]")
isJSON := i != -1
if isJSON {
networks := []map[string]interface{}{}
err := json.Unmarshal([]byte(existingValue), &networks)
if err != nil {
// existingValue is not valid JSON; nothing we can do but skip injection
log.Warnf("Unable to unmarshal Multus Network annotation JSON value: %v", err)
return existingValue
}
for _, net := range networks {
if net["name"] == istioCniNetwork {
return existingValue
}
}
return existingValue[0:i] + fmt.Sprintf(`, {"name": "%s"}`, istioCniNetwork) + existingValue[i:]
}
for _, net := range strings.Split(existingValue, ",") {
if strings.TrimSpace(net) == istioCniNetwork {
return existingValue
}
}
return existingValue + ", " + istioCniNetwork
}
// this function is no longer used by the template but kept around for backwards compatibility
func applicationPorts(containers []corev1.Container) string {
return getContainerPorts(containers, func(c corev1.Container) bool {
return c.Name != ProxyContainerName
})
}
func includeInboundPorts(containers []corev1.Container) string {
// Include the ports from all containers in the deployment.
return getContainerPorts(containers, func(corev1.Container) bool { return true })
}
func getPortsForContainer(container corev1.Container) []string {
parts := make([]string, 0)
for _, p := range container.Ports {
if p.Protocol == corev1.ProtocolUDP || p.Protocol == corev1.ProtocolSCTP {
continue
}
parts = append(parts, strconv.Itoa(int(p.ContainerPort)))
}
return parts
}
func getContainerPorts(containers []corev1.Container, shouldIncludePorts func(corev1.Container) bool) string {
parts := make([]string, 0)
for _, c := range containers {
if shouldIncludePorts(c) {
parts = append(parts, getPortsForContainer(c)...)
}
}
return strings.Join(parts, ",")
}
func kubevirtInterfaces(s string) string {
return s
}
func structToJSON(v interface{}) string {
if v == nil {
return "{}"
}
ba, err := json.Marshal(v)
if err != nil {
log.Warnf("Unable to marshal %v", v)
return "{}"
}
return string(ba)
}
func protoToJSON(v proto.Message) string {
v = cleanProxyConfig(v)
if v == nil {
return "{}"
}
ba, err := protomarshal.ToJSON(v)
if err != nil {
log.Warnf("Unable to marshal %v: %v", v, err)
return "{}"
}
return ba
}
// Rather than dump the entire proxy config, we remove fields that are default
// This makes the pod spec much smaller
// This is not comprehensive code, but nothing will break if this misses some fields
func cleanProxyConfig(msg proto.Message) proto.Message {
originalProxyConfig, ok := msg.(*meshconfig.ProxyConfig)
if !ok || originalProxyConfig == nil {
return msg
}
pc := proto.Clone(originalProxyConfig).(*meshconfig.ProxyConfig)
defaults := mesh.DefaultProxyConfig()
if pc.ConfigPath == defaults.ConfigPath {
pc.ConfigPath = ""
}
if pc.BinaryPath == defaults.BinaryPath {
pc.BinaryPath = ""
}
if pc.ControlPlaneAuthPolicy == defaults.ControlPlaneAuthPolicy {
pc.ControlPlaneAuthPolicy = 0
}
if x, ok := pc.GetClusterName().(*meshconfig.ProxyConfig_ServiceCluster); ok {
if x.ServiceCluster == defaults.GetClusterName().(*meshconfig.ProxyConfig_ServiceCluster).ServiceCluster {
pc.ClusterName = nil
}
}
if proto.Equal(pc.DrainDuration, defaults.DrainDuration) {
pc.DrainDuration = nil
}
if proto.Equal(pc.TerminationDrainDuration, defaults.TerminationDrainDuration) {
pc.TerminationDrainDuration = nil
}
if proto.Equal(pc.ParentShutdownDuration, defaults.ParentShutdownDuration) {
pc.ParentShutdownDuration = nil
}
if pc.DiscoveryAddress == defaults.DiscoveryAddress {
pc.DiscoveryAddress = ""
}
if proto.Equal(pc.EnvoyMetricsService, defaults.EnvoyMetricsService) {
pc.EnvoyMetricsService = nil
}
if proto.Equal(pc.EnvoyAccessLogService, defaults.EnvoyAccessLogService) {
pc.EnvoyAccessLogService = nil
}
if proto.Equal(pc.Tracing, defaults.Tracing) {
pc.Tracing = nil
}
if pc.ProxyAdminPort == defaults.ProxyAdminPort {
pc.ProxyAdminPort = 0
}
if pc.StatNameLength == defaults.StatNameLength {
pc.StatNameLength = 0
}
if pc.StatusPort == defaults.StatusPort {
pc.StatusPort = 0
}
if proto.Equal(pc.Concurrency, defaults.Concurrency) {
pc.Concurrency = nil
}
if len(pc.ProxyMetadata) == 0 {
pc.ProxyMetadata = nil
}
return proto.Message(pc)
}