| // 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 bootstrap |
| |
| import ( |
| "encoding/json" |
| "errors" |
| "fmt" |
| "os" |
| "path" |
| "strconv" |
| "strings" |
| ) |
| |
| import ( |
| core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" |
| "google.golang.org/protobuf/types/known/structpb" |
| "google.golang.org/protobuf/types/known/wrapperspb" |
| "istio.io/api/annotation" |
| meshAPI "istio.io/api/mesh/v1alpha1" |
| "istio.io/pkg/env" |
| "istio.io/pkg/log" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/pilot/pkg/features" |
| "github.com/apache/dubbo-go-pixiu/pilot/pkg/model" |
| "github.com/apache/dubbo-go-pixiu/pilot/pkg/networking/util" |
| "github.com/apache/dubbo-go-pixiu/pilot/pkg/util/network" |
| "github.com/apache/dubbo-go-pixiu/pkg/bootstrap/option" |
| "github.com/apache/dubbo-go-pixiu/pkg/bootstrap/platform" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/constants" |
| "github.com/apache/dubbo-go-pixiu/pkg/kube/labels" |
| "github.com/apache/dubbo-go-pixiu/pkg/util/protomarshal" |
| "github.com/apache/dubbo-go-pixiu/pkg/util/sets" |
| ) |
| |
| const ( |
| // IstioMetaPrefix is used to pass env vars as node metadata. |
| IstioMetaPrefix = "ISTIO_META_" |
| |
| // IstioMetaJSONPrefix is used to pass annotations and similar environment info. |
| IstioMetaJSONPrefix = "ISTIO_METAJSON_" |
| |
| lightstepAccessTokenBase = "lightstep_access_token.txt" |
| |
| // required stats are used by readiness checks. |
| requiredEnvoyStatsMatcherInclusionPrefixes = "cluster_manager,listener_manager,server,cluster.xds-grpc,wasm" |
| |
| rbacEnvoyStatsMatcherInclusionSuffix = "rbac.allowed,rbac.denied,shadow_allowed,shadow_denied" |
| |
| requiredEnvoyStatsMatcherInclusionSuffixes = rbacEnvoyStatsMatcherInclusionSuffix + ",downstream_cx_active" // Needed for draining. |
| |
| // Prefixes of V2 metrics. |
| // "reporter" prefix is for istio standard metrics. |
| // "component" suffix is for istio_build metric. |
| v2Prefixes = "reporter=," |
| v2Suffix = ",component" |
| ) |
| |
| // Config for creating a bootstrap file. |
| type Config struct { |
| *model.Node |
| } |
| |
| // toTemplateParams creates a new template configuration for the given configuration. |
| func (cfg Config) toTemplateParams() (map[string]interface{}, error) { |
| opts := make([]option.Instance, 0) |
| |
| discHost := strings.Split(cfg.Metadata.ProxyConfig.DiscoveryAddress, ":")[0] |
| |
| xdsType := "GRPC" |
| if features.DeltaXds { |
| xdsType = "DELTA_GRPC" |
| } |
| |
| opts = append(opts, |
| option.NodeID(cfg.ID), |
| option.NodeType(cfg.ID), |
| option.PilotSubjectAltName(cfg.Metadata.PilotSubjectAltName), |
| option.OutlierLogPath(cfg.Metadata.OutlierLogPath), |
| option.ProvCert(cfg.Metadata.ProvCert), |
| option.DiscoveryHost(discHost), |
| option.Metadata(cfg.Metadata), |
| option.XdsType(xdsType)) |
| |
| // Add GCPProjectNumber to access in bootstrap template. |
| md := cfg.Metadata.PlatformMetadata |
| if projectNumber, found := md[platform.GCPProjectNumber]; found { |
| opts = append(opts, option.GCPProjectNumber(projectNumber)) |
| } |
| |
| if cfg.Metadata.StsPort != "" { |
| stsPort, err := strconv.Atoi(cfg.Metadata.StsPort) |
| if err == nil && stsPort > 0 { |
| opts = append(opts, |
| option.STSEnabled(true), |
| option.STSPort(stsPort)) |
| md := cfg.Metadata.PlatformMetadata |
| if projectID, found := md[platform.GCPProject]; found { |
| opts = append(opts, option.GCPProjectID(projectID)) |
| } |
| } |
| } |
| |
| // Support passing extra info from node environment as metadata |
| opts = append(opts, getNodeMetadataOptions(cfg.Node)...) |
| |
| // Check if nodeIP carries IPv4 or IPv6 and set up proxy accordingly |
| if network.AllIPv6(cfg.Metadata.InstanceIPs) { |
| opts = append(opts, |
| option.Localhost(option.LocalhostIPv6), |
| option.Wildcard(option.WildcardIPv6), |
| option.DNSLookupFamily(option.DNSLookupFamilyIPv6)) |
| } else { |
| opts = append(opts, |
| option.Localhost(option.LocalhostIPv4), |
| option.Wildcard(option.WildcardIPv4), |
| option.DNSLookupFamily(option.DNSLookupFamilyIPv4)) |
| } |
| |
| proxyOpts, err := getProxyConfigOptions(cfg.Metadata) |
| if err != nil { |
| return nil, err |
| } |
| opts = append(opts, proxyOpts...) |
| |
| // TODO: allow reading a file with additional metadata (for example if created with |
| // 'envref'. This will allow Istio to generate the right config even if the pod info |
| // is not available (in particular in some multi-cluster cases) |
| return option.NewTemplateParams(opts...) |
| } |
| |
| // substituteValues substitutes variables known to the bootstrap like pod_ip. |
| // "http.{pod_ip}_" with pod_id = [10.3.3.3,10.4.4.4] --> [http.10.3.3.3_,http.10.4.4.4_] |
| func substituteValues(patterns []string, varName string, values []string) []string { |
| ret := make([]string, 0, len(patterns)) |
| for _, pattern := range patterns { |
| if !strings.Contains(pattern, varName) { |
| ret = append(ret, pattern) |
| continue |
| } |
| |
| for _, val := range values { |
| ret = append(ret, strings.Replace(pattern, varName, val, -1)) |
| } |
| } |
| return ret |
| } |
| |
| // DefaultStatTags for telemetry v2 tag extraction. |
| var DefaultStatTags = []string{ |
| "reporter", |
| "source_namespace", |
| "source_workload", |
| "source_workload_namespace", |
| "source_principal", |
| "source_app", |
| "source_version", |
| "source_cluster", |
| "destination_namespace", |
| "destination_workload", |
| "destination_workload_namespace", |
| "destination_principal", |
| "destination_app", |
| "destination_version", |
| "destination_service", |
| "destination_service_name", |
| "destination_service_namespace", |
| "destination_port", |
| "destination_cluster", |
| "request_protocol", |
| "request_operation", |
| "request_host", |
| "response_flags", |
| "grpc_response_status", |
| "connection_security_policy", |
| "source_canonical_service", |
| "destination_canonical_service", |
| "source_canonical_revision", |
| "destination_canonical_revision", |
| } |
| |
| func getStatsOptions(meta *model.BootstrapNodeMetadata) []option.Instance { |
| nodeIPs := meta.InstanceIPs |
| config := meta.ProxyConfig |
| |
| tagAnno := meta.Annotations[annotation.SidecarExtraStatTags.Name] |
| prefixAnno := meta.Annotations[annotation.SidecarStatsInclusionPrefixes.Name] |
| RegexAnno := meta.Annotations[annotation.SidecarStatsInclusionRegexps.Name] |
| suffixAnno := meta.Annotations[annotation.SidecarStatsInclusionSuffixes.Name] |
| |
| parseOption := func(metaOption string, required string, proxyConfigOption []string) []string { |
| var inclusionOption []string |
| if len(metaOption) > 0 { |
| inclusionOption = strings.Split(metaOption, ",") |
| } else if proxyConfigOption != nil { |
| // In case user relies on mixed usage of annotation and proxy config, |
| // only consider proxy config if annotation is not set instead of merging. |
| inclusionOption = proxyConfigOption |
| } |
| |
| if len(required) > 0 { |
| inclusionOption = append(inclusionOption, strings.Split(required, ",")...) |
| } |
| |
| // At the sidecar we can limit downstream metrics collection to the inbound listener. |
| // Inbound downstream metrics are named as: http.{pod_ip}_{port}.downstream_rq_* |
| // Other outbound downstream metrics are numerous and not very interesting for a sidecar. |
| // specifying http.{pod_ip}_ as a prefix will capture these downstream metrics. |
| return substituteValues(inclusionOption, "{pod_ip}", nodeIPs) |
| } |
| |
| extraStatTags := make([]string, 0, len(DefaultStatTags)) |
| extraStatTags = append(extraStatTags, |
| DefaultStatTags...) |
| for _, tag := range config.ExtraStatTags { |
| if tag != "" { |
| extraStatTags = append(extraStatTags, tag) |
| } |
| } |
| for _, tag := range strings.Split(tagAnno, ",") { |
| if tag != "" { |
| extraStatTags = append(extraStatTags, tag) |
| } |
| } |
| extraStatTags = removeDuplicates(extraStatTags) |
| |
| var proxyConfigPrefixes, proxyConfigSuffixes, proxyConfigRegexps []string |
| if config.ProxyStatsMatcher != nil { |
| proxyConfigPrefixes = config.ProxyStatsMatcher.InclusionPrefixes |
| proxyConfigSuffixes = config.ProxyStatsMatcher.InclusionSuffixes |
| proxyConfigRegexps = config.ProxyStatsMatcher.InclusionRegexps |
| } |
| inclusionSuffixes := rbacEnvoyStatsMatcherInclusionSuffix |
| if meta.ExitOnZeroActiveConnections { |
| inclusionSuffixes = requiredEnvoyStatsMatcherInclusionSuffixes |
| } |
| |
| return []option.Instance{ |
| option.EnvoyStatsMatcherInclusionPrefix(parseOption(prefixAnno, |
| requiredEnvoyStatsMatcherInclusionPrefixes, proxyConfigPrefixes)), |
| option.EnvoyStatsMatcherInclusionSuffix(parseOption(suffixAnno, |
| inclusionSuffixes, proxyConfigSuffixes)), |
| option.EnvoyStatsMatcherInclusionRegexp(parseOption(RegexAnno, "", proxyConfigRegexps)), |
| option.EnvoyExtraStatTags(extraStatTags), |
| } |
| } |
| |
| func lightstepAccessTokenFile(config string) string { |
| return path.Join(config, lightstepAccessTokenBase) |
| } |
| |
| func getNodeMetadataOptions(node *model.Node) []option.Instance { |
| // Add locality options. |
| opts := getLocalityOptions(node.Locality) |
| |
| opts = append(opts, getStatsOptions(node.Metadata)...) |
| |
| opts = append(opts, |
| option.NodeMetadata(node.Metadata, node.RawMetadata), |
| option.RuntimeFlags(extractRuntimeFlags(node.Metadata.ProxyConfig)), |
| option.EnvoyStatusPort(node.Metadata.EnvoyStatusPort), |
| option.EnvoyPrometheusPort(node.Metadata.EnvoyPrometheusPort)) |
| return opts |
| } |
| |
| var StripFragment = env.RegisterBoolVar("HTTP_STRIP_FRAGMENT_FROM_PATH_UNSAFE_IF_DISABLED", true, "").Get() |
| |
| func extractRuntimeFlags(cfg *model.NodeMetaProxyConfig) map[string]string { |
| // Setup defaults |
| runtimeFlags := map[string]string{ |
| "overload.global_downstream_max_connections": "2147483647", |
| "envoy.deprecated_features:envoy.config.listener.v3.Listener.hidden_envoy_deprecated_use_original_dst": "true", |
| "envoy.reloadable_features.require_strict_1xx_and_204_response_headers": "false", |
| "re2.max_program_size.error_level": "32768", |
| "envoy.reloadable_features.http_reject_path_with_fragment": "false", |
| "envoy.reloadable_features.no_extension_lookup_by_name": "false", |
| } |
| if !StripFragment { |
| // Note: the condition here is basically backwards. This was a mistake in the initial commit and cannot be reverted |
| runtimeFlags["envoy.reloadable_features.http_strip_fragment_from_path_unsafe_if_disabled"] = "false" |
| } |
| for k, v := range cfg.RuntimeValues { |
| if v == "" { |
| // Envoy runtime doesn't see "" as a special value, so we use it to mean 'unset default flag' |
| delete(runtimeFlags, k) |
| continue |
| } |
| runtimeFlags[k] = v |
| } |
| return runtimeFlags |
| } |
| |
| func getLocalityOptions(l *core.Locality) []option.Instance { |
| return []option.Instance{option.Region(l.Region), option.Zone(l.Zone), option.SubZone(l.SubZone)} |
| } |
| |
| func getServiceCluster(metadata *model.BootstrapNodeMetadata) string { |
| switch name := metadata.ProxyConfig.ClusterName.(type) { |
| case *meshAPI.ProxyConfig_ServiceCluster: |
| return serviceClusterOrDefault(name.ServiceCluster, metadata) |
| |
| case *meshAPI.ProxyConfig_TracingServiceName_: |
| workloadName := metadata.WorkloadName |
| if workloadName == "" { |
| workloadName = "istio-proxy" |
| } |
| |
| switch name.TracingServiceName { |
| case meshAPI.ProxyConfig_APP_LABEL_AND_NAMESPACE: |
| return serviceClusterOrDefault("istio-proxy", metadata) |
| case meshAPI.ProxyConfig_CANONICAL_NAME_ONLY: |
| cs, _ := labels.CanonicalService(metadata.Labels, workloadName) |
| return serviceClusterOrDefault(cs, metadata) |
| case meshAPI.ProxyConfig_CANONICAL_NAME_AND_NAMESPACE: |
| cs, _ := labels.CanonicalService(metadata.Labels, workloadName) |
| if metadata.Namespace != "" { |
| return cs + "." + metadata.Namespace |
| } |
| return serviceClusterOrDefault(cs, metadata) |
| default: |
| return serviceClusterOrDefault("istio-proxy", metadata) |
| } |
| |
| default: |
| return serviceClusterOrDefault("istio-proxy", metadata) |
| } |
| } |
| |
| func serviceClusterOrDefault(name string, metadata *model.BootstrapNodeMetadata) string { |
| if name != "" && name != "istio-proxy" { |
| return name |
| } |
| if app, ok := metadata.Labels["app"]; ok { |
| return app + "." + metadata.Namespace |
| } |
| if metadata.WorkloadName != "" { |
| return metadata.WorkloadName + "." + metadata.Namespace |
| } |
| if metadata.Namespace != "" { |
| return "istio-proxy." + metadata.Namespace |
| } |
| return "istio-proxy" |
| } |
| |
| func getProxyConfigOptions(metadata *model.BootstrapNodeMetadata) ([]option.Instance, error) { |
| config := metadata.ProxyConfig |
| |
| // Add a few misc options. |
| opts := make([]option.Instance, 0) |
| |
| opts = append(opts, option.ProxyConfig(config), |
| option.Cluster(getServiceCluster(metadata)), |
| option.PilotGRPCAddress(config.DiscoveryAddress), |
| option.DiscoveryAddress(config.DiscoveryAddress), |
| option.StatsdAddress(config.StatsdUdpAddress), |
| option.XDSRootCert(metadata.XDSRootCert)) |
| |
| // Add tracing options. |
| if config.Tracing != nil { |
| isH2 := false |
| switch tracer := config.Tracing.Tracer.(type) { |
| case *meshAPI.Tracing_Zipkin_: |
| opts = append(opts, option.ZipkinAddress(tracer.Zipkin.Address)) |
| case *meshAPI.Tracing_Lightstep_: |
| isH2 = true |
| // Create the token file. |
| lightstepAccessTokenPath := lightstepAccessTokenFile(config.ConfigPath) |
| lsConfigOut, err := os.Create(lightstepAccessTokenPath) |
| if err != nil { |
| return nil, err |
| } |
| _, err = lsConfigOut.WriteString(tracer.Lightstep.AccessToken) |
| if err != nil { |
| return nil, err |
| } |
| |
| opts = append(opts, option.LightstepAddress(tracer.Lightstep.Address), |
| option.LightstepToken(lightstepAccessTokenPath)) |
| case *meshAPI.Tracing_Datadog_: |
| opts = append(opts, option.DataDogAddress(tracer.Datadog.Address)) |
| case *meshAPI.Tracing_Stackdriver_: |
| projectID, projFound := metadata.PlatformMetadata[platform.GCPProject] |
| if !projFound { |
| return nil, errors.New("unable to process Stackdriver tracer: missing GCP Project") |
| } |
| |
| opts = append(opts, option.StackDriverEnabled(true), |
| option.StackDriverProjectID(projectID), |
| option.StackDriverDebug(tracer.Stackdriver.Debug), |
| option.StackDriverMaxAnnotations(getInt64ValueOrDefault(tracer.Stackdriver.MaxNumberOfAnnotations, 200)), |
| option.StackDriverMaxAttributes(getInt64ValueOrDefault(tracer.Stackdriver.MaxNumberOfAttributes, 200)), |
| option.StackDriverMaxEvents(getInt64ValueOrDefault(tracer.Stackdriver.MaxNumberOfMessageEvents, 200))) |
| case *meshAPI.Tracing_OpenCensusAgent_: |
| c := tracer.OpenCensusAgent.Context |
| opts = append(opts, option.OpenCensusAgentAddress(tracer.OpenCensusAgent.Address), |
| option.OpenCensusAgentContexts(c)) |
| } |
| |
| opts = append(opts, option.TracingTLS(config.Tracing.TlsSettings, metadata, isH2)) |
| } |
| |
| // Add options for Envoy metrics. |
| if config.EnvoyMetricsService != nil && config.EnvoyMetricsService.Address != "" { |
| opts = append(opts, option.EnvoyMetricsServiceAddress(config.EnvoyMetricsService.Address), |
| option.EnvoyMetricsServiceTLS(config.EnvoyMetricsService.TlsSettings, metadata), |
| option.EnvoyMetricsServiceTCPKeepalive(config.EnvoyMetricsService.TcpKeepalive)) |
| } else if config.EnvoyMetricsServiceAddress != "" { // nolint: staticcheck |
| opts = append(opts, option.EnvoyMetricsServiceAddress(config.EnvoyMetricsService.Address)) |
| } |
| |
| // Add options for Envoy access log. |
| if config.EnvoyAccessLogService != nil && config.EnvoyAccessLogService.Address != "" { |
| opts = append(opts, option.EnvoyAccessLogServiceAddress(config.EnvoyAccessLogService.Address), |
| option.EnvoyAccessLogServiceTLS(config.EnvoyAccessLogService.TlsSettings, metadata), |
| option.EnvoyAccessLogServiceTCPKeepalive(config.EnvoyAccessLogService.TcpKeepalive)) |
| } |
| |
| return opts, nil |
| } |
| |
| func getInt64ValueOrDefault(src *wrapperspb.Int64Value, defaultVal int64) int64 { |
| val := defaultVal |
| if src != nil { |
| val = src.Value |
| } |
| return val |
| } |
| |
| type setMetaFunc func(m map[string]interface{}, key string, val string) |
| |
| func extractMetadata(envs []string, prefix string, set setMetaFunc, meta map[string]interface{}) { |
| metaPrefixLen := len(prefix) |
| for _, e := range envs { |
| if !shouldExtract(e, prefix) { |
| continue |
| } |
| v := e[metaPrefixLen:] |
| if !isEnvVar(v) { |
| continue |
| } |
| metaKey, metaVal := parseEnvVar(v) |
| set(meta, metaKey, metaVal) |
| } |
| } |
| |
| func shouldExtract(envVar, prefix string) bool { |
| return strings.HasPrefix(envVar, prefix) |
| } |
| |
| func isEnvVar(str string) bool { |
| return strings.Contains(str, "=") |
| } |
| |
| func parseEnvVar(varStr string) (string, string) { |
| parts := strings.SplitN(varStr, "=", 2) |
| if len(parts) != 2 { |
| return varStr, "" |
| } |
| return parts[0], parts[1] |
| } |
| |
| func jsonStringToMap(jsonStr string) (m map[string]string) { |
| err := json.Unmarshal([]byte(jsonStr), &m) |
| if err != nil { |
| log.Warnf("Env variable with value %q failed json unmarshal: %v", jsonStr, err) |
| } |
| return |
| } |
| |
| func extractAttributesMetadata(envVars []string, plat platform.Environment, meta *model.BootstrapNodeMetadata) { |
| for _, varStr := range envVars { |
| name, val := parseEnvVar(varStr) |
| switch name { |
| case "ISTIO_METAJSON_LABELS": |
| m := jsonStringToMap(val) |
| if len(m) > 0 { |
| meta.Labels = m |
| } |
| case "POD_NAME": |
| meta.InstanceName = val |
| case "POD_NAMESPACE": |
| meta.Namespace = val |
| case "SERVICE_ACCOUNT": |
| meta.ServiceAccount = val |
| } |
| } |
| if plat != nil && len(plat.Metadata()) > 0 { |
| meta.PlatformMetadata = plat.Metadata() |
| } |
| } |
| |
| // MetadataOptions for constructing node metadata. |
| type MetadataOptions struct { |
| Envs []string |
| Platform platform.Environment |
| InstanceIPs []string |
| StsPort int |
| ID string |
| ProxyConfig *meshAPI.ProxyConfig |
| PilotSubjectAltName []string |
| XDSRootCert string |
| OutlierLogPath string |
| ProvCert string |
| annotationFilePath string |
| EnvoyStatusPort int |
| EnvoyPrometheusPort int |
| ExitOnZeroActiveConnections bool |
| } |
| |
| // GetNodeMetaData function uses an environment variable contract |
| // ISTIO_METAJSON_* env variables contain json_string in the value. |
| // The name of variable is ignored. |
| // ISTIO_META_* env variables are passed thru |
| func GetNodeMetaData(options MetadataOptions) (*model.Node, error) { |
| meta := &model.BootstrapNodeMetadata{} |
| untypedMeta := map[string]interface{}{} |
| |
| extractMetadata(options.Envs, IstioMetaPrefix, func(m map[string]interface{}, key string, val string) { |
| m[key] = val |
| }, untypedMeta) |
| |
| extractMetadata(options.Envs, IstioMetaJSONPrefix, func(m map[string]interface{}, key string, val string) { |
| err := json.Unmarshal([]byte(val), &m) |
| if err != nil { |
| log.Warnf("Env variable %s [%s] failed json unmarshal: %v", key, val, err) |
| } |
| }, untypedMeta) |
| |
| j, err := json.Marshal(untypedMeta) |
| if err != nil { |
| return nil, err |
| } |
| |
| if err := json.Unmarshal(j, meta); err != nil { |
| return nil, err |
| } |
| extractAttributesMetadata(options.Envs, options.Platform, meta) |
| |
| // Support multiple network interfaces, removing duplicates. |
| meta.InstanceIPs = removeDuplicates(options.InstanceIPs) |
| |
| // Add STS port into node metadata if it is not 0. This is read by envoy telemetry filters |
| if options.StsPort != 0 { |
| meta.StsPort = strconv.Itoa(options.StsPort) |
| } |
| meta.EnvoyStatusPort = options.EnvoyStatusPort |
| meta.EnvoyPrometheusPort = options.EnvoyPrometheusPort |
| meta.ExitOnZeroActiveConnections = model.StringBool(options.ExitOnZeroActiveConnections) |
| |
| meta.ProxyConfig = (*model.NodeMetaProxyConfig)(options.ProxyConfig) |
| |
| // Add all instance labels with lower precedence than pod labels |
| extractInstanceLabels(options.Platform, meta) |
| |
| // Add all pod labels found from filesystem |
| // These are typically volume mounted by the downward API |
| lbls, err := readPodLabels() |
| if err == nil { |
| if meta.Labels == nil { |
| meta.Labels = map[string]string{} |
| } |
| for k, v := range lbls { |
| meta.Labels[k] = v |
| } |
| } else { |
| if os.IsNotExist(err) { |
| log.Debugf("failed to read pod labels: %v", err) |
| } else { |
| log.Warnf("failed to read pod labels: %v", err) |
| } |
| } |
| |
| // Add all pod annotations found from filesystem |
| // These are typically volume mounted by the downward API |
| annos, err := ReadPodAnnotations(options.annotationFilePath) |
| if err == nil { |
| if meta.Annotations == nil { |
| meta.Annotations = map[string]string{} |
| } |
| for k, v := range annos { |
| meta.Annotations[k] = v |
| } |
| } else { |
| if os.IsNotExist(err) { |
| log.Debugf("failed to read pod annotations: %v", err) |
| } else { |
| log.Warnf("failed to read pod annotations: %v", err) |
| } |
| } |
| |
| var l *core.Locality |
| if meta.Labels[model.LocalityLabel] == "" && options.Platform != nil { |
| // The locality string was not set, try to get locality from platform |
| l = options.Platform.Locality() |
| } else { |
| localityString := model.GetLocalityLabelOrDefault(meta.Labels[model.LocalityLabel], "") |
| l = util.ConvertLocality(localityString) |
| } |
| |
| meta.PilotSubjectAltName = options.PilotSubjectAltName |
| meta.XDSRootCert = options.XDSRootCert |
| meta.OutlierLogPath = options.OutlierLogPath |
| meta.ProvCert = options.ProvCert |
| |
| return &model.Node{ |
| ID: options.ID, |
| Metadata: meta, |
| RawMetadata: untypedMeta, |
| Locality: l, |
| }, nil |
| } |
| |
| // ConvertNodeToXDSNode creates an Envoy node descriptor from Istio node descriptor. |
| func ConvertNodeToXDSNode(node *model.Node) *core.Node { |
| // First pass translates typed metadata |
| js, err := json.Marshal(node.Metadata) |
| if err != nil { |
| log.Warnf("Failed to marshal node metadata to JSON %#v: %v", node.Metadata, err) |
| } |
| pbst := &structpb.Struct{} |
| if err = protomarshal.Unmarshal(js, pbst); err != nil { |
| log.Warnf("Failed to unmarshal node metadata from JSON %#v: %v", node.Metadata, err) |
| } |
| // Second pass translates untyped metadata for "unknown" fields |
| for k, v := range node.RawMetadata { |
| if _, f := pbst.Fields[k]; !f { |
| fjs, err := json.Marshal(v) |
| if err != nil { |
| log.Warnf("Failed to marshal field metadata to JSON %#v: %v", k, err) |
| } |
| pbv := &structpb.Value{} |
| if err = protomarshal.Unmarshal(fjs, pbv); err != nil { |
| log.Warnf("Failed to unmarshal field metadata from JSON %#v: %v", k, err) |
| } |
| pbst.Fields[k] = pbv |
| } |
| } |
| return &core.Node{ |
| Id: node.ID, |
| Cluster: getServiceCluster(node.Metadata), |
| Locality: node.Locality, |
| Metadata: pbst, |
| } |
| } |
| |
| // ConvertXDSNodeToNode parses Istio node descriptor from an Envoy node descriptor, using only typed metadata. |
| func ConvertXDSNodeToNode(node *core.Node) *model.Node { |
| b, err := protomarshal.MarshalProtoNames(node.Metadata) |
| if err != nil { |
| log.Warnf("Failed to marshal node metadata to JSON %q: %v", node.Metadata, err) |
| } |
| metadata := &model.BootstrapNodeMetadata{} |
| err = json.Unmarshal(b, metadata) |
| if err != nil { |
| log.Warnf("Failed to unmarshal node metadata from JSON %q: %v", node.Metadata, err) |
| } |
| if metadata.ProxyConfig == nil { |
| metadata.ProxyConfig = &model.NodeMetaProxyConfig{} |
| metadata.ProxyConfig.ClusterName = &meshAPI.ProxyConfig_ServiceCluster{ServiceCluster: node.Cluster} |
| } |
| |
| return &model.Node{ |
| ID: node.Id, |
| Locality: node.Locality, |
| Metadata: metadata, |
| } |
| } |
| |
| // Extracts instance labels for the platform into model.NodeMetadata.Labels |
| // only if not running on Kubernetes |
| func extractInstanceLabels(plat platform.Environment, meta *model.BootstrapNodeMetadata) { |
| if plat == nil || meta == nil || plat.IsKubernetes() { |
| return |
| } |
| instanceLabels := plat.Labels() |
| if meta.Labels == nil { |
| meta.Labels = map[string]string{} |
| } |
| for k, v := range instanceLabels { |
| meta.Labels[k] = v |
| } |
| } |
| |
| func readPodLabels() (map[string]string, error) { |
| b, err := os.ReadFile(constants.PodInfoLabelsPath) |
| if err != nil { |
| return nil, err |
| } |
| return ParseDownwardAPI(string(b)) |
| } |
| |
| func ReadPodAnnotations(path string) (map[string]string, error) { |
| if path == "" { |
| path = constants.PodInfoAnnotationsPath |
| } |
| b, err := os.ReadFile(path) |
| if err != nil { |
| return nil, err |
| } |
| return ParseDownwardAPI(string(b)) |
| } |
| |
| // ParseDownwardAPI parses fields which are stored as format `%s=%q` back to a map |
| func ParseDownwardAPI(i string) (map[string]string, error) { |
| res := map[string]string{} |
| for _, line := range strings.Split(i, "\n") { |
| sl := strings.SplitN(line, "=", 2) |
| if len(sl) != 2 { |
| continue |
| } |
| key := sl[0] |
| // Strip the leading/trailing quotes |
| val, err := strconv.Unquote(sl[1]) |
| if err != nil { |
| return nil, fmt.Errorf("failed to unquote %v: %v", sl[1], err) |
| } |
| res[key] = val |
| } |
| return res, nil |
| } |
| |
| func removeDuplicates(values []string) []string { |
| set := sets.New() |
| newValues := make([]string, 0, len(values)) |
| for _, v := range values { |
| if !set.Contains(v) { |
| set.Insert(v) |
| newValues = append(newValues, v) |
| } |
| } |
| return newValues |
| } |