blob: ee3c0605b17efa84f3d87f04301bf5b84cdc35e5 [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 model
import (
"regexp"
"strings"
)
import (
"google.golang.org/protobuf/proto"
networking "istio.io/api/networking/v1alpha3"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/labels"
"github.com/apache/dubbo-go-pixiu/pkg/config/xds"
"github.com/apache/dubbo-go-pixiu/pkg/util/sets"
)
// EnvoyFilterWrapper is a wrapper for the EnvoyFilter api object with pre-processed data
type EnvoyFilterWrapper struct {
Name string
Namespace string
workloadSelector labels.Instance
Patches map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper
}
// EnvoyFilterConfigPatchWrapper is a wrapper over the EnvoyFilter ConfigPatch api object
// fields are ordered such that this struct is aligned
type EnvoyFilterConfigPatchWrapper struct {
Value proto.Message
Match *networking.EnvoyFilter_EnvoyConfigObjectMatch
ApplyTo networking.EnvoyFilter_ApplyTo
Operation networking.EnvoyFilter_Patch_Operation
// Pre-compile the regex from proxy version match in the match
ProxyVersionRegex *regexp.Regexp
// ProxyPrefixMatch provides a prefix match for the proxy version. The current API only allows
// regex match, but as an optimization we can reduce this to a prefix match for common cases.
// If this is set, ProxyVersionRegex is ignored.
ProxyPrefixMatch string
Name string
Namespace string
}
// wellKnownVersions defines a mapping of well known regex matches to prefix matches
// This is done only as an optimization; behavior should remain the same
// All versions specified by the default installation (Telemetry V2) should be added here.
var wellKnownVersions = map[string]string{
`^1\.4.*`: "1.4",
`^1\.5.*`: "1.5",
`^1\.6.*`: "1.6",
`^1\.7.*`: "1.7",
`^1\.8.*`: "1.8",
`^1\.9.*`: "1.9",
`^1\.10.*`: "1.10",
`^1\.11.*`: "1.11",
`^1\.12.*`: "1.12",
`^1\.13.*`: "1.13",
`^1\.14.*`: "1.14",
`^1\.15.*`: "1.15",
`^1\.16.*`: "1.16",
`^1\.17.*`: "1.17",
`^1\.18.*`: "1.18",
`^1\.19.*`: "1.19",
// Hopefully we have a better API by 1.19. If not, add it here
}
// convertToEnvoyFilterWrapper converts from EnvoyFilter config to EnvoyFilterWrapper object
func convertToEnvoyFilterWrapper(local *config.Config) *EnvoyFilterWrapper {
localEnvoyFilter := local.Spec.(*networking.EnvoyFilter)
out := &EnvoyFilterWrapper{Name: local.Name, Namespace: local.Namespace}
if localEnvoyFilter.WorkloadSelector != nil {
out.workloadSelector = localEnvoyFilter.WorkloadSelector.Labels
}
out.Patches = make(map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper)
for _, cp := range localEnvoyFilter.ConfigPatches {
if cp.Patch == nil {
// Should be caught by validation, but sometimes its disabled and we don't want to crash
// as a result.
log.Debugf("envoyfilter %v/%v discarded due to missing patch", local.Namespace, local.Name)
continue
}
cpw := &EnvoyFilterConfigPatchWrapper{
Name: local.Name,
Namespace: local.Namespace,
ApplyTo: cp.ApplyTo,
Match: cp.Match,
Operation: cp.Patch.Operation,
}
var err error
// Use non-strict building to avoid issues where EnvoyFilter is valid but meant
// for a different version of the API than we are built with
cpw.Value, err = xds.BuildXDSObjectFromStruct(cp.ApplyTo, cp.Patch.Value, false)
// There generally won't be an error here because validation catches mismatched types
// Should only happen in tests or without validation
if err != nil {
log.Errorf("failed to build envoy filter value: %v", err)
}
if cp.Match == nil {
// create a match all object
cpw.Match = &networking.EnvoyFilter_EnvoyConfigObjectMatch{Context: networking.EnvoyFilter_ANY}
} else if cp.Match.Proxy != nil && cp.Match.Proxy.ProxyVersion != "" {
// Attempt to convert regex to a simple prefix match for the common case of matching
// a standard Istio version. This field should likely be replaced with semver, but for now
// we can workaround the performance impact of regex
if prefix, f := wellKnownVersions[cp.Match.Proxy.ProxyVersion]; f {
cpw.ProxyPrefixMatch = prefix
} else {
// pre-compile the regex for proxy version if it exists
// ignore the error because validation catches invalid regular expressions.
cpw.ProxyVersionRegex, _ = regexp.Compile(cp.Match.Proxy.ProxyVersion)
}
}
if _, exists := out.Patches[cp.ApplyTo]; !exists {
out.Patches[cp.ApplyTo] = make([]*EnvoyFilterConfigPatchWrapper, 0)
}
if cpw.Operation == networking.EnvoyFilter_Patch_INSERT_AFTER ||
cpw.Operation == networking.EnvoyFilter_Patch_INSERT_BEFORE ||
cpw.Operation == networking.EnvoyFilter_Patch_INSERT_FIRST {
// insert_before, after or first is applicable for network filter,
// http filter and http route, convert the rest to add
if cpw.ApplyTo != networking.EnvoyFilter_HTTP_FILTER &&
cpw.ApplyTo != networking.EnvoyFilter_NETWORK_FILTER &&
cpw.ApplyTo != networking.EnvoyFilter_HTTP_ROUTE {
cpw.Operation = networking.EnvoyFilter_Patch_ADD
}
}
out.Patches[cp.ApplyTo] = append(out.Patches[cp.ApplyTo], cpw)
}
return out
}
func proxyMatch(proxy *Proxy, cp *EnvoyFilterConfigPatchWrapper) bool {
if cp.Match.Proxy == nil {
return true
}
if cp.ProxyPrefixMatch != "" {
if !strings.HasPrefix(proxy.Metadata.IstioVersion, cp.ProxyPrefixMatch) {
return false
}
}
if cp.ProxyVersionRegex != nil {
ver := proxy.Metadata.IstioVersion
if ver == "" {
// we do not have a proxy version but the user has a regex. so this is a mismatch
return false
}
if !cp.ProxyVersionRegex.MatchString(ver) {
return false
}
}
for k, v := range cp.Match.Proxy.Metadata {
if proxy.Metadata.Raw[k] != v {
return false
}
}
return true
}
// Returns the keys of all the wrapped envoyfilters.
func (efw *EnvoyFilterWrapper) Keys() []string {
if efw == nil {
return nil
}
keys := sets.Set{}
for _, patches := range efw.Patches {
for _, patch := range patches {
keys.Insert(patch.Key())
}
}
return keys.SortedList()
}
func (cpw *EnvoyFilterConfigPatchWrapper) Key() string {
if cpw == nil {
return ""
}
return cpw.Namespace + "/" + cpw.Name
}