From 7376339a3c920b6b11cb4ecf8419bf59315a62fe Mon Sep 17 00:00:00 2001 From: Shriram Rajagopalan Date: Sat, 27 Jul 2019 12:45:00 -0400 Subject: [PATCH] EnvoyFilter: match filter chains, http/network filters (#15639) * Match http/network filters Signed-off-by: Shriram Rajagopalan * insert before or after Signed-off-by: Shriram Rajagopalan * split into smaller files Signed-off-by: Shriram Rajagopalan * tests and lint Signed-off-by: Shriram Rajagopalan * test Signed-off-by: Shriram Rajagopalan * lint Signed-off-by: Shriram Rajagopalan * lots of tests Signed-off-by: Shriram Rajagopalan * lint1 * test fixes * lint * disable until resolution * lint * update gogo Signed-off-by: Shriram Rajagopalan * unskip tests Signed-off-by: Shriram Rajagopalan * lint Signed-off-by: Shriram Rajagopalan * integration test Signed-off-by: Shriram Rajagopalan * config fixes Signed-off-by: Shriram Rajagopalan * bug fix Signed-off-by: Shriram Rajagopalan * lint Signed-off-by: Shriram Rajagopalan --- go.mod | 7 +- go.sum | 9 +- pilot/pkg/model/envoyfilter.go | 21 +- pilot/pkg/networking/core/v1alpha3/cluster.go | 22 +- .../networking/core/v1alpha3/cluster_test.go | 4 +- .../networking/core/v1alpha3/envoyfilter.go | 807 --------------- .../v1alpha3/envoyfilter/cluster_patch.go | 104 ++ .../envoyfilter/cluster_patch_test.go | 297 ++++++ .../core/v1alpha3/envoyfilter/deprecated.go | 303 ++++++ .../v1alpha3/envoyfilter/deprecated_test.go | 159 +++ .../v1alpha3/envoyfilter/listener_patch.go | 480 +++++++++ .../envoyfilter/listener_patch_test.go | 635 ++++++++++++ .../core/v1alpha3/envoyfilter/rc_patch.go | 168 +++ .../v1alpha3/envoyfilter/rc_patch_test.go | 411 ++++++++ .../core/v1alpha3/envoyfilter_test.go | 973 ------------------ pilot/pkg/networking/core/v1alpha3/gateway.go | 9 +- .../networking/core/v1alpha3/gateway_test.go | 6 +- .../pkg/networking/core/v1alpha3/httproute.go | 19 +- .../pkg/networking/core/v1alpha3/listener.go | 141 +-- .../core/v1alpha3/listener_builder.go | 32 +- .../networking/v1alpha3/envoyfilter-c.yaml | 43 +- .../github.com/gogo/protobuf/jsonpb/jsonpb.go | 7 +- .../gogo/protobuf/proto/extensions.go | 1 + .../gogo/protobuf/proto/extensions_gogo.go | 21 + .../gogo/protobuf/proto/table_merge.go | 19 + .../github.com/gogo/protobuf/types/any.pb.go | 50 +- .../github.com/gogo/protobuf/types/api.pb.go | 309 +++--- .../gogo/protobuf/types/duration.pb.go | 44 +- .../gogo/protobuf/types/empty.pb.go | 30 +- .../gogo/protobuf/types/field_mask.pb.go | 50 +- .../gogo/protobuf/types/source_context.pb.go | 41 +- .../gogo/protobuf/types/struct.pb.go | 254 +++-- .../gogo/protobuf/types/timestamp.pb.go | 44 +- .../github.com/gogo/protobuf/types/type.pb.go | 508 +++++---- .../gogo/protobuf/types/wrappers.pb.go | 242 +++-- .../google/go-cmp/cmp/cmpopts/ignore.go | 62 ++ .../google/go-cmp/cmp/cmpopts/sort.go | 33 +- .../google/go-cmp/cmp/cmpopts/sort_go17.go | 46 - .../google/go-cmp/cmp/cmpopts/sort_go18.go | 31 - .../google/go-cmp/cmp/cmpopts/xform.go | 35 + .../github.com/google/go-cmp/cmp/compare.go | 557 +++++----- .../cmp/{unsafe_panic.go => export_panic.go} | 6 +- .../{unsafe_reflect.go => export_unsafe.go} | 8 +- .../go-cmp/cmp/internal/diff/debug_disable.go | 2 +- .../go-cmp/cmp/internal/diff/debug_enable.go | 4 +- .../google/go-cmp/cmp/internal/diff/diff.go | 31 +- .../google/go-cmp/cmp/internal/flags/flags.go | 9 + .../cmp/internal/flags/toolchain_legacy.go | 10 + .../cmp/internal/flags/toolchain_recent.go | 10 + .../go-cmp/cmp/internal/function/func.go | 64 +- .../go-cmp/cmp/internal/value/format.go | 277 ----- .../cmp/internal/value/pointer_purego.go | 23 + .../cmp/internal/value/pointer_unsafe.go | 26 + .../google/go-cmp/cmp/internal/value/sort.go | 9 +- .../google/go-cmp/cmp/internal/value/zero.go | 45 + .../github.com/google/go-cmp/cmp/options.go | 255 +++-- vendor/github.com/google/go-cmp/cmp/path.go | 339 +++--- vendor/github.com/google/go-cmp/cmp/report.go | 51 + .../google/go-cmp/cmp/report_compare.go | 296 ++++++ .../google/go-cmp/cmp/report_reflect.go | 279 +++++ .../google/go-cmp/cmp/report_slices.go | 333 ++++++ .../google/go-cmp/cmp/report_text.go | 382 +++++++ .../google/go-cmp/cmp/report_value.go | 121 +++ .../github.com/google/go-cmp/cmp/reporter.go | 53 - vendor/github.com/lukechampine/freeze/LICENSE | 21 - .../github.com/lukechampine/freeze/README.md | 112 -- .../github.com/lukechampine/freeze/freeze.go | 290 ------ vendor/modules.txt | 9 +- 68 files changed, 6140 insertions(+), 3959 deletions(-) delete mode 100644 pilot/pkg/networking/core/v1alpha3/envoyfilter.go create mode 100644 pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch.go create mode 100644 pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch_test.go create mode 100644 pilot/pkg/networking/core/v1alpha3/envoyfilter/deprecated.go create mode 100644 pilot/pkg/networking/core/v1alpha3/envoyfilter/deprecated_test.go create mode 100644 pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch.go create mode 100644 pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch_test.go create mode 100644 pilot/pkg/networking/core/v1alpha3/envoyfilter/rc_patch.go create mode 100644 pilot/pkg/networking/core/v1alpha3/envoyfilter/rc_patch_test.go delete mode 100644 pilot/pkg/networking/core/v1alpha3/envoyfilter_test.go delete mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go delete mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go create mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/xform.go rename vendor/github.com/google/go-cmp/cmp/{unsafe_panic.go => export_panic.go} (60%) rename vendor/github.com/google/go-cmp/cmp/{unsafe_reflect.go => export_unsafe.go} (64%) create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go delete mode 100644 vendor/github.com/google/go-cmp/cmp/internal/value/format.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go create mode 100644 vendor/github.com/google/go-cmp/cmp/internal/value/zero.go create mode 100644 vendor/github.com/google/go-cmp/cmp/report.go create mode 100644 vendor/github.com/google/go-cmp/cmp/report_compare.go create mode 100644 vendor/github.com/google/go-cmp/cmp/report_reflect.go create mode 100644 vendor/github.com/google/go-cmp/cmp/report_slices.go create mode 100644 vendor/github.com/google/go-cmp/cmp/report_text.go create mode 100644 vendor/github.com/google/go-cmp/cmp/report_value.go delete mode 100644 vendor/github.com/google/go-cmp/cmp/reporter.go delete mode 100644 vendor/github.com/lukechampine/freeze/LICENSE delete mode 100644 vendor/github.com/lukechampine/freeze/README.md delete mode 100644 vendor/github.com/lukechampine/freeze/freeze.go diff --git a/go.mod b/go.mod index 26b5b06423cc..ae3b98e312e2 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,8 @@ replace k8s.io/klog => github.com/istio/klog v0.0.0-20190424230111-fb7481ea8bcf replace github.com/spf13/viper => github.com/istio/viper v1.3.3-0.20190515210538-2789fed3109c +replace github.com/gogo/protobuf => github.com/istio/gogo-protobuf v1.2.2-0.20190726125433-4c9abdb3090c + require ( cloud.google.com/go v0.37.4 contrib.go.opencensus.io/exporter/prometheus v0.1.0 @@ -78,7 +80,7 @@ require ( github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f github.com/google/btree v1.0.0 // indirect github.com/google/cel-go v0.2.0 - github.com/google/go-cmp v0.2.0 + github.com/google/go-cmp v0.3.0 github.com/google/go-github v15.0.0+incompatible github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect @@ -109,6 +111,7 @@ require ( github.com/howeyc/fsnotify v0.9.0 github.com/huandu/xstrings v1.0.0 // indirect github.com/imdario/mergo v0.3.5 // indirect + github.com/istio/gogo-protobuf v1.2.2-0.20190726125433-4c9abdb3090c // indirect github.com/jefferai/jsonx v1.0.0 // indirect github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 // indirect github.com/json-iterator/go v0.0.0-20180914014843-2433035e5132 // indirect @@ -119,7 +122,7 @@ require ( github.com/kr/pretty v0.1.0 // indirect github.com/lestrrat-go/jwx v0.9.0 github.com/lib/pq v1.1.1 // indirect - github.com/lukechampine/freeze v0.0.0-20160818180733-f514e08ae5a0 + github.com/lukechampine/freeze v0.0.0-20160818180733-f514e08ae5a0 // indirect github.com/mitchellh/copystructure v1.0.0 github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 github.com/mitchellh/go-testing-interface v1.0.0 // indirect diff --git a/go.sum b/go.sum index 76a864a4adba..c78c06b943d9 100644 --- a/go.sum +++ b/go.sum @@ -189,6 +189,8 @@ github.com/google/cel-go v0.2.0/go.mod h1:fTCVOuSN/Vn6d49zvRpr3fDAKFyfpLViE0gU+9 github.com/google/cel-spec v0.2.0/go.mod h1:MjQm800JAGhOZXI7vatnVpmIaFTR6L8FHcKk+piiKpI= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-github v15.0.0+incompatible h1:jlPg2Cpsxb/FyEV/MFiIE9tW/2RAevQNZDPeHbf5a94= github.com/google/go-github v15.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= @@ -286,6 +288,9 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/istio/glog v0.0.0-20190424172949-d7cfb6fa2ccd h1:AJnLAbRpHRy2stiICRI1hcp0f8EExPEiZLgeOmCGnuE= github.com/istio/glog v0.0.0-20190424172949-d7cfb6fa2ccd/go.mod h1:gF8UB8w1Mqkddo9AqNOPkiduBosB3HHkpC5ra96cDzw= +github.com/istio/gogo-protobuf v1.2.1 h1:8CFvH2ygdNeWdQ7/PaA5ilexEJCO1okGNDL7cqP9Ldo= +github.com/istio/gogo-protobuf v1.2.2-0.20190726125433-4c9abdb3090c h1:nZ1jQm7wyjt21Zc6c1jtf3RVH375/Omgzq+INkXOkWg= +github.com/istio/gogo-protobuf v1.2.2-0.20190726125433-4c9abdb3090c/go.mod h1:vfjn3j3y5/f0PSoVc2mvjpkYi6onwEz0eC1iLQrg8yY= github.com/istio/klog v0.0.0-20190424230111-fb7481ea8bcf h1:AshFubsUWsHMYfGoz5XLZOOF87wnop5O/Fjjnqjk8lY= github.com/istio/klog v0.0.0-20190424230111-fb7481ea8bcf/go.mod h1:9gnFtvcm4y+2DZMNXbO8Q7Ke2kUDomg7HhR/mEs5wVA= github.com/istio/viper v1.3.3-0.20190515210538-2789fed3109c h1:EFWADU43GY2T7NIYYbIHWdrG2hRiWyGSHeON57ZADBE= @@ -309,6 +314,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/keybase/go-crypto v0.0.0-20190416182011-b785b22cc757 h1:rHXu79NFmin5AvIe4JsnfCBGb1qAIlMTX0vnpVnDn7s= github.com/keybase/go-crypto v0.0.0-20190416182011-b785b22cc757/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -547,6 +553,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c h1:vamGzbGri8IKo20MQncCuljcQ5uAO6kaCeawQPVblAI= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -611,8 +618,6 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= istio.io/api v0.0.0-20190515205759-982e5c3888c6/go.mod h1:hhLFQmpHia8zgaM37vb2ml9iS5NfNfqZGRt1pS9aVEo= -istio.io/api v0.0.0-20190716182821-d90184ef5a47 h1:aTfqvytJfCKvZaIr/8jnqBM7uLWLVbyZQQplSL6glvA= -istio.io/api v0.0.0-20190716182821-d90184ef5a47/go.mod h1:hhLFQmpHia8zgaM37vb2ml9iS5NfNfqZGRt1pS9aVEo= istio.io/api v0.0.0-20190718213450-0a0442bf8664 h1:6qd2tnoFRDkqIcuf7rBOAIoZ9F62nuT/k7xGBUMHfQ8= istio.io/api v0.0.0-20190718213450-0a0442bf8664/go.mod h1:hhLFQmpHia8zgaM37vb2ml9iS5NfNfqZGRt1pS9aVEo= istio.io/gogo-genproto v0.0.0-20190614210408-e88dc8b0e4db h1:a++JUbz/eKj16759379pFBhuoiSxUTmnut6ITM/9FEs= diff --git a/pilot/pkg/model/envoyfilter.go b/pilot/pkg/model/envoyfilter.go index e461c40c38bd..fca17b3001c9 100644 --- a/pilot/pkg/model/envoyfilter.go +++ b/pilot/pkg/model/envoyfilter.go @@ -23,7 +23,7 @@ import ( // EnvoyFilterWrapper is a wrapper for the EnvoyFilter api object with pre-processed data type EnvoyFilterWrapper struct { workloadSelector config.Labels - ConfigPatches []*EnvoyFilterConfigPatchWrapper + Patches map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper } // EnvoyFilterConfigPatchWrapper is a wrapper over the EnvoyFilter ConfigPatch api object @@ -43,7 +43,7 @@ func convertToEnvoyFilterWrapper(local *Config) *EnvoyFilterWrapper { if localEnvoyFilter.WorkloadSelector != nil { out.workloadSelector = config.Labels(localEnvoyFilter.WorkloadSelector.Labels) } - out.ConfigPatches = make([]*EnvoyFilterConfigPatchWrapper, 0, len(localEnvoyFilter.ConfigPatches)) + out.Patches = make(map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper) for _, cp := range localEnvoyFilter.ConfigPatches { cpw := &EnvoyFilterConfigPatchWrapper{ ApplyTo: cp.ApplyTo, @@ -52,7 +52,22 @@ func convertToEnvoyFilterWrapper(local *Config) *EnvoyFilterWrapper { } // there wont be an error here because validation catches mismatched types cpw.Value, _ = config.BuildXDSObjectFromStruct(cp.ApplyTo, cp.Patch.Value) - out.ConfigPatches = append(out.ConfigPatches, cpw) + if cp.Match == nil { + // create a match all object + cpw.Match = &networking.EnvoyFilter_EnvoyConfigObjectMatch{Context: networking.EnvoyFilter_ANY} + } + 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 { + // insert_before or after is applicable only for network filter and http filter + // convert the rest to add + if cpw.ApplyTo != networking.EnvoyFilter_HTTP_FILTER && cpw.ApplyTo != networking.EnvoyFilter_NETWORK_FILTER { + cpw.Operation = networking.EnvoyFilter_Patch_ADD + } + } + out.Patches[cp.ApplyTo] = append(out.Patches[cp.ApplyTo], cpw) } return out } diff --git a/pilot/pkg/networking/core/v1alpha3/cluster.go b/pilot/pkg/networking/core/v1alpha3/cluster.go index 8fe0befcdc83..b34ee6a1a3ff 100644 --- a/pilot/pkg/networking/core/v1alpha3/cluster.go +++ b/pilot/pkg/networking/core/v1alpha3/cluster.go @@ -33,6 +33,7 @@ import ( "istio.io/istio/pilot/pkg/features" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pilot/pkg/networking/core/v1alpha3/envoyfilter" "istio.io/istio/pilot/pkg/networking/core/v1alpha3/loadbalancer" "istio.io/istio/pilot/pkg/networking/plugin" "istio.io/istio/pilot/pkg/networking/util" @@ -80,33 +81,38 @@ func (configgen *ConfigGeneratorImpl) BuildClusters(env *model.Environment, prox clusters := make([]*apiv2.Cluster, 0) instances := proxy.ServiceInstances - clusters = append(clusters, configgen.buildOutboundClusters(env, proxy, push)...) + outboundClusters := configgen.buildOutboundClusters(env, proxy, push) if env.Mesh.LocalityLbSetting != nil { // apply load balancer setting fot cluster endpoints - applyLocalityLBSetting(proxy.Locality, clusters, env.Mesh.LocalityLbSetting) + applyLocalityLBSetting(proxy.Locality, outboundClusters, env.Mesh.LocalityLbSetting) } + // Add a blackhole and passthrough cluster for catching traffic to unresolved routes + // DO NOT CALL PLUGINS for these two clusters. + outboundClusters = append(outboundClusters, buildBlackHoleCluster(env), buildDefaultPassthroughCluster(env)) switch proxy.Type { case model.SidecarProxy: + outboundClusters = envoyfilter.ApplyClusterPatches(networking.EnvoyFilter_SIDECAR_OUTBOUND, proxy, push, outboundClusters) // Let ServiceDiscovery decide which IP and Port are used for management if // there are multiple IPs managementPorts := make([]*model.Port, 0) for _, ip := range proxy.IPAddresses { managementPorts = append(managementPorts, env.ManagementPorts(ip)...) } - clusters = append(clusters, configgen.buildInboundClusters(env, proxy, push, instances, managementPorts)...) + inboundClusters := configgen.buildInboundClusters(env, proxy, push, instances, managementPorts) + inboundClusters = envoyfilter.ApplyClusterPatches(networking.EnvoyFilter_SIDECAR_INBOUND, proxy, push, inboundClusters) + clusters = append(clusters, outboundClusters...) + clusters = append(clusters, inboundClusters...) default: // Gateways if proxy.Type == model.Router && proxy.GetRouterMode() == model.SniDnatRouter { - clusters = append(clusters, configgen.buildOutboundSniDnatClusters(env, proxy, push)...) + outboundClusters = append(outboundClusters, configgen.buildOutboundSniDnatClusters(env, proxy, push)...) } + outboundClusters = envoyfilter.ApplyClusterPatches(networking.EnvoyFilter_GATEWAY, proxy, push, outboundClusters) + clusters = outboundClusters } - // Add a blackhole and passthrough cluster for catching traffic to unresolved routes - // DO NOT CALL PLUGINS for these two clusters. - clusters = append(clusters, buildBlackHoleCluster(env), buildDefaultPassthroughCluster(env)) - clusters = applyClusterPatches(env, proxy, push, clusters) clusters = normalizeClusters(push, proxy, clusters) return clusters, nil diff --git a/pilot/pkg/networking/core/v1alpha3/cluster_test.go b/pilot/pkg/networking/core/v1alpha3/cluster_test.go index c64492b1646b..adf596b063a1 100644 --- a/pilot/pkg/networking/core/v1alpha3/cluster_test.go +++ b/pilot/pkg/networking/core/v1alpha3/cluster_test.go @@ -68,7 +68,7 @@ func TestHTTPCircuitBreakerThresholds(t *testing.T) { clusterIndex: 0, }, { direction: model.TrafficDirectionInbound, - clusterIndex: 1, + clusterIndex: 3, }, } settings := []*networking.ConnectionPoolSettings{ @@ -135,7 +135,7 @@ func TestCommonHttpProtocolOptions(t *testing.T) { clusterIndex: 0, }, { direction: model.TrafficDirectionInbound, - clusterIndex: 1, + clusterIndex: 3, }, } settings := &networking.ConnectionPoolSettings{ diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter.go deleted file mode 100644 index 9735052c9aef..000000000000 --- a/pilot/pkg/networking/core/v1alpha3/envoyfilter.go +++ /dev/null @@ -1,807 +0,0 @@ -// Copyright 2018 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 v1alpha3 - -import ( - "net" - "strings" - - xdsapi "github.com/envoyproxy/go-control-plane/envoy/api/v2" - "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" - "github.com/envoyproxy/go-control-plane/envoy/api/v2/listener" - "github.com/envoyproxy/go-control-plane/envoy/api/v2/route" - http_conn "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2" - xdsutil "github.com/envoyproxy/go-control-plane/pkg/util" - "github.com/gogo/protobuf/proto" - - networking "istio.io/api/networking/v1alpha3" - "istio.io/istio/pilot/pkg/model" - "istio.io/istio/pilot/pkg/networking/plugin" - "istio.io/istio/pilot/pkg/networking/util" - "istio.io/istio/pkg/config" - "istio.io/pkg/log" -) - -// We process EnvoyFilter CRDs after calling all plugins and building the listener with the required filter chains -// Having the entire filter chain is essential because users can specify one or more filters to be inserted -// before/after a filter or remove one or more filters. -// We use the plugin.InputParams as a convenience object to pass around parameters like proxy, proxyInstances, ports, -// etc., instead of having a long argument list -// If one or more filters are added to the HTTP connection manager, we will update the last filter in the listener -// filter chain (which is the http connection manager) with the updated object. -func deprecatedInsertUserFilters(in *plugin.InputParams, listener *xdsapi.Listener, - httpConnectionManagers []*http_conn.HttpConnectionManager) error { //nolint: unparam - filterCRD := getUserFiltersForWorkload(in.Env, in.Node.WorkloadLabels) - if filterCRD == nil { - return nil - } - - listenerIPAddress := getListenerIPAddress(&listener.Address) - if listenerIPAddress == nil { - log.Warnf("Failed to parse IP Address from plugin listener") - } - - for _, f := range filterCRD.Filters { - if !deprecatedListenerMatch(in, listenerIPAddress, f.ListenerMatch) { - continue - } - // 4 cases of filter insertion - // http listener, http filter - // tcp listener, tcp filter - // http listener, tcp filter - // tcp listener, http filter -- invalid - - for cnum, lFilterChain := range listener.FilterChains { - if util.IsHTTPFilterChain(lFilterChain) { - // The listener match logic does not take into account the listener protocol - // because filter chains in a listener can have multiple protocols. - // for each filter chain, if the filter chain has a http connection manager, - // treat it as a http listener - // ListenerProtocol defaults to ALL. But if user specified listener protocol TCP, then - // skip this filter chain as its a HTTP filter chain - if f.ListenerMatch != nil && - !(f.ListenerMatch.ListenerProtocol == networking.EnvoyFilter_DeprecatedListenerMatch_ALL || - f.ListenerMatch.ListenerProtocol == networking.EnvoyFilter_DeprecatedListenerMatch_HTTP) { - continue - } - - // Now that the match condition is true, insert the filter if compatible - // http listener, http filter case - if f.FilterType == networking.EnvoyFilter_Filter_HTTP { - // Insert into http connection manager - deprecatedInsertHTTPFilter(listener.Name, &listener.FilterChains[cnum], httpConnectionManagers[cnum], f, util.IsXDSMarshalingToAnyEnabled(in.Node)) - } else { - // http listener, tcp filter - deprecatedInsertNetworkFilter(listener.Name, &listener.FilterChains[cnum], f) - } - } else { - // The listener match logic does not take into account the listener protocol - // because filter chains in a listener can have multiple protocols. - // for each filter chain, if the filter chain does not have a http connection manager, - // treat it as a tcp listener - // ListenerProtocol defaults to ALL. But if user specified listener protocol HTTP, then - // skip this filter chain as its a TCP filter chain - if f.ListenerMatch != nil && - !(f.ListenerMatch.ListenerProtocol == networking.EnvoyFilter_DeprecatedListenerMatch_ALL || - f.ListenerMatch.ListenerProtocol == networking.EnvoyFilter_DeprecatedListenerMatch_TCP) { - continue - } - - // treat both as insert network filter X into network filter chain. - // We cannot insert a HTTP in filter in network filter chain. - // Even HTTP connection manager is a network filter - if f.FilterType == networking.EnvoyFilter_Filter_HTTP { - log.Warnf("Ignoring filter %s. Cannot insert HTTP filter in network filter chain", - f.FilterName) - continue - } - deprecatedInsertNetworkFilter(listener.Name, &listener.FilterChains[cnum], f) - } - } - } - return nil -} - -// NOTE: There can be only one filter for a workload. If multiple filters are defined, the behavior -// is undefined. -func getUserFiltersForWorkload(env *model.Environment, labels config.LabelsCollection) *networking.EnvoyFilter { - f := env.EnvoyFilter(labels) - if f != nil { - return f.Spec.(*networking.EnvoyFilter) - } - return nil -} - -func getListenerIPAddress(address *core.Address) net.IP { - if address != nil && address.Address != nil { - switch t := address.Address.(type) { - case *core.Address_SocketAddress: - if t.SocketAddress != nil { - ip := "0.0.0.0" - if t.SocketAddress.Address != "::" { - ip = t.SocketAddress.Address - } - return net.ParseIP(ip) - } - } - } - return nil -} - -func deprecatedListenerMatch(in *plugin.InputParams, listenerIP net.IP, - matchCondition *networking.EnvoyFilter_DeprecatedListenerMatch) bool { - if matchCondition == nil { - return true - } - - // match by port first - if matchCondition.PortNumber > 0 && in.Port.Port != int(matchCondition.PortNumber) { - return false - } - - // match by port name prefix - if matchCondition.PortNamePrefix != "" { - if !strings.HasPrefix(strings.ToLower(in.Port.Name), strings.ToLower(matchCondition.PortNamePrefix)) { - return false - } - } - - // case ANY implies do not care about proxy type or direction - if matchCondition.ListenerType != networking.EnvoyFilter_DeprecatedListenerMatch_ANY { - // check if the current listener category matches with the user specified type - if matchCondition.ListenerType != in.DeprecatedListenerCategory { - return false - } - - // Check if the node's role matches properly with the listener category - switch matchCondition.ListenerType { - case networking.EnvoyFilter_DeprecatedListenerMatch_GATEWAY: - if in.Node.Type != model.Router { - return false // We don't care about direction for gateways - } - case networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_INBOUND, - networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND: - if in.Node.Type != model.SidecarProxy { - return false - } - } - } - - // Listener protocol will be matched as we try to insert the filters - - if len(matchCondition.Address) > 0 { - matched := false - // if any of the addresses here match, return true - for _, address := range matchCondition.Address { - if strings.Contains(address, "/") { - var ipNet *net.IPNet - var err error - if _, ipNet, err = net.ParseCIDR(address); err != nil { - log.Warnf("Failed to parse address %s in EnvoyFilter: %v", address, err) - continue - } - if ipNet.Contains(listenerIP) { - matched = true - break - } - } else if net.ParseIP(address).Equal(listenerIP) { - matched = true - break - } - } - return matched - } - - return true -} - -func deprecatedInsertHTTPFilter(listenerName string, filterChain *listener.FilterChain, hcm *http_conn.HttpConnectionManager, - envoyFilter *networking.EnvoyFilter_Filter, isXDSMarshalingToAnyEnabled bool) { - filter := &http_conn.HttpFilter{ - Name: envoyFilter.FilterName, - ConfigType: &http_conn.HttpFilter_Config{Config: envoyFilter.FilterConfig}, - } - - position := networking.EnvoyFilter_InsertPosition_FIRST - if envoyFilter.InsertPosition != nil { - position = envoyFilter.InsertPosition.Index - } - - oldLen := len(hcm.HttpFilters) - switch position { - case networking.EnvoyFilter_InsertPosition_FIRST, networking.EnvoyFilter_InsertPosition_BEFORE: - hcm.HttpFilters = append([]*http_conn.HttpFilter{filter}, hcm.HttpFilters...) - if position == networking.EnvoyFilter_InsertPosition_BEFORE { - // bubble the filter to the right position scanning from beginning - for i := 1; i < len(hcm.HttpFilters); i++ { - if hcm.HttpFilters[i].Name != envoyFilter.InsertPosition.RelativeTo { - hcm.HttpFilters[i-1], hcm.HttpFilters[i] = hcm.HttpFilters[i], hcm.HttpFilters[i-1] - } else { - break - } - } - } - case networking.EnvoyFilter_InsertPosition_LAST, networking.EnvoyFilter_InsertPosition_AFTER: - hcm.HttpFilters = append(hcm.HttpFilters, filter) - if position == networking.EnvoyFilter_InsertPosition_AFTER { - // bubble the filter to the right position scanning from end - for i := len(hcm.HttpFilters) - 2; i >= 0; i-- { - if hcm.HttpFilters[i].Name != envoyFilter.InsertPosition.RelativeTo { - hcm.HttpFilters[i+1], hcm.HttpFilters[i] = hcm.HttpFilters[i], hcm.HttpFilters[i+1] - } else { - break - } - } - } - } - - // Rebuild the HTTP connection manager in the network filter chain - // Its the last filter in the filter chain - filterStruct := listener.Filter{ - Name: xdsutil.HTTPConnectionManager, - } - if isXDSMarshalingToAnyEnabled { - filterStruct.ConfigType = &listener.Filter_TypedConfig{TypedConfig: util.MessageToAny(hcm)} - } else { - filterStruct.ConfigType = &listener.Filter_Config{Config: util.MessageToStruct(hcm)} - } - filterChain.Filters[len(filterChain.Filters)-1] = filterStruct - log.Infof("EnvoyFilters: Rebuilt HTTP Connection Manager %s (from %d filters to %d filters)", - listenerName, oldLen, len(hcm.HttpFilters)) -} - -func deprecatedInsertNetworkFilter(listenerName string, filterChain *listener.FilterChain, - envoyFilter *networking.EnvoyFilter_Filter) { - filter := &listener.Filter{ - Name: envoyFilter.FilterName, - ConfigType: &listener.Filter_Config{Config: envoyFilter.FilterConfig}, - } - - position := networking.EnvoyFilter_InsertPosition_FIRST - if envoyFilter.InsertPosition != nil { - position = envoyFilter.InsertPosition.Index - } - - oldLen := len(filterChain.Filters) - switch position { - case networking.EnvoyFilter_InsertPosition_FIRST, networking.EnvoyFilter_InsertPosition_BEFORE: - filterChain.Filters = append([]listener.Filter{*filter}, filterChain.Filters...) - if position == networking.EnvoyFilter_InsertPosition_BEFORE { - // bubble the filter to the right position scanning from beginning - for i := 1; i < len(filterChain.Filters); i++ { - if filterChain.Filters[i].Name != envoyFilter.InsertPosition.RelativeTo { - filterChain.Filters[i-1], filterChain.Filters[i] = filterChain.Filters[i], filterChain.Filters[i-1] - break - } - } - } - case networking.EnvoyFilter_InsertPosition_LAST, networking.EnvoyFilter_InsertPosition_AFTER: - filterChain.Filters = append(filterChain.Filters, *filter) - if position == networking.EnvoyFilter_InsertPosition_AFTER { - // bubble the filter to the right position scanning from end - for i := len(filterChain.Filters) - 2; i >= 0; i-- { - if filterChain.Filters[i].Name != envoyFilter.InsertPosition.RelativeTo { - filterChain.Filters[i+1], filterChain.Filters[i] = filterChain.Filters[i], filterChain.Filters[i+1] - break - } - } - } - } - log.Infof("EnvoyFilters: Rebuilt network filter stack for listener %s (from %d filters to %d filters)", - listenerName, oldLen, len(filterChain.Filters)) -} - -func applyClusterPatches(_ *model.Environment, proxy *model.Proxy, - push *model.PushContext, clusters []*xdsapi.Cluster) []*xdsapi.Cluster { - - envoyFilterWrappers := push.EnvoyFilters(proxy) - clustersRemoved := false - for _, efw := range envoyFilterWrappers { - // First process remove operations, then the merge and finally the add. - // If add is done before remove, then remove could end up deleting a cluster that - // was added by the user. - for _, cp := range efw.ConfigPatches { - if cp.ApplyTo != networking.EnvoyFilter_CLUSTER { - continue - } - - if cp.Operation != networking.EnvoyFilter_Patch_REMOVE { - continue - } - - for i := range clusters { - if clusters[i] == nil { - // deleted by the remove operation - continue - } - - if clusterMatch(proxy, clusters[i], cp.Match, cp.Operation) { - clusters[i] = nil - clustersRemoved = true - } - } - } - - for _, cp := range efw.ConfigPatches { - if cp.ApplyTo != networking.EnvoyFilter_CLUSTER { - continue - } - - if cp.Operation != networking.EnvoyFilter_Patch_MERGE { - continue - } - - for i := range clusters { - if clusters[i] == nil { - // deleted by the remove operation - continue - } - if clusterMatch(proxy, clusters[i], cp.Match, cp.Operation) { - proto.Merge(clusters[i], cp.Value) - } - } - } - - // Add cluster if the operation is add, and patch context matches - for _, cp := range efw.ConfigPatches { - if cp.ApplyTo != networking.EnvoyFilter_CLUSTER { - continue - } - - if cp.Operation != networking.EnvoyFilter_Patch_ADD { - continue - } - - if clusterMatch(proxy, nil, cp.Match, cp.Operation) { - clusters = append(clusters, cp.Value.(*xdsapi.Cluster)) - } - } - } - - if clustersRemoved { - trimmedClusters := make([]*xdsapi.Cluster, 0, len(clusters)) - for i := range clusters { - if clusters[i] == nil { - continue - } - trimmedClusters = append(trimmedClusters, clusters[i]) - } - clusters = trimmedClusters - } - return clusters -} - -func applyListenerPatches(proxy *model.Proxy, push *model.PushContext, builder *ListenerBuilder) *ListenerBuilder { - envoyFilterWrappers := push.EnvoyFilters(proxy) - mergeRemoveListener := func(patchContext networking.EnvoyFilter_PatchContext, - listener *xdsapi.Listener, cp *model.EnvoyFilterConfigPatchWrapper, objectsRemoved *bool) { - if listenerMatch(patchContext, listener, cp) { - if cp.Operation == networking.EnvoyFilter_Patch_REMOVE { - listener.Name = "" - *objectsRemoved = true - } else if cp.Operation == networking.EnvoyFilter_Patch_MERGE { - proto.Merge(listener, cp.Value) - } - } - } - // utility function to remove deleted listeners and rebuild the array if needed. - rebuildListenerArray := func(listeners []*xdsapi.Listener) []*xdsapi.Listener { - tempArray := make([]*xdsapi.Listener, 0, len(listeners)) - for _, l := range listeners { - if l.Name != "" { - tempArray = append(tempArray, l) - } - } - return tempArray - } - doListenerOperation := func(patchContext networking.EnvoyFilter_PatchContext, listener *xdsapi.Listener, objectsRemoved *bool) { - for _, efw := range envoyFilterWrappers { - for _, cp := range efw.ConfigPatches { - switch cp.ApplyTo { - case networking.EnvoyFilter_LISTENER: - if cp.Operation != networking.EnvoyFilter_Patch_REMOVE && - cp.Operation != networking.EnvoyFilter_Patch_MERGE { - // we will add stuff in the end - continue - } - mergeRemoveListener(patchContext, listener, cp, objectsRemoved) - case networking.EnvoyFilter_FILTER_CHAIN: - // handle adds, removes, merges - case networking.EnvoyFilter_NETWORK_FILTER: - // handle adds, inserts, removes, merges - case networking.EnvoyFilter_HTTP_FILTER: - // match on http conn man and handle adds, inserts, removes, merges - } - } - } - } - doListenerListOperation := func(patchContext networking.EnvoyFilter_PatchContext, listeners []*xdsapi.Listener) []*xdsapi.Listener { - objectsRemoved := false - for _, listener := range listeners { - doListenerOperation(patchContext, listener, &objectsRemoved) - } - // now the adds at listener level - for _, efw := range envoyFilterWrappers { - for _, cp := range efw.ConfigPatches { - // todo inserts - if cp.ApplyTo != networking.EnvoyFilter_LISTENER || cp.Operation != networking.EnvoyFilter_Patch_ADD { - continue - } - if listenerMatch(patchContext, nil, cp) { - listeners = append(listeners, cp.Value.(*xdsapi.Listener)) - } - } - } - if objectsRemoved { - return rebuildListenerArray(listeners) - } - return listeners - } - - if proxy.Type == model.Router { - builder.gatewayListeners = doListenerListOperation(networking.EnvoyFilter_GATEWAY, builder.gatewayListeners) - return builder - } - - // this is all for sidecar - if builder.virtualInboundListener != nil { - removed := false - doListenerOperation(networking.EnvoyFilter_SIDECAR_INBOUND, builder.virtualInboundListener, &removed) - if removed { - builder.virtualInboundListener = nil - } - } - if builder.virtualListener != nil { - removed := false - doListenerOperation(networking.EnvoyFilter_SIDECAR_OUTBOUND, builder.virtualListener, &removed) - if removed { - builder.virtualListener = nil - } - } - - builder.inboundListeners = doListenerListOperation(networking.EnvoyFilter_SIDECAR_INBOUND, builder.inboundListeners) - builder.managementListeners = doListenerListOperation(networking.EnvoyFilter_SIDECAR_INBOUND, builder.managementListeners) - builder.outboundListeners = doListenerListOperation(networking.EnvoyFilter_SIDECAR_OUTBOUND, builder.outboundListeners) - - return builder -} - -func applyRouteConfigurationPatches(pluginParams *plugin.InputParams, - routeConfiguration *xdsapi.RouteConfiguration) *xdsapi.RouteConfiguration { - - virtualHostsRemoved := false - envoyFilterWrappers := pluginParams.Push.EnvoyFilters(pluginParams.Node) - for _, efw := range envoyFilterWrappers { - // remove & add are not applicable for route configuration but applicable for virtual hosts - // remove virtual host if there is operation is remove, and matches - - // First process remove operations, then the merge and finally the add. - // If add is done before remove, then remove could end up deleting a vhost that - // was added by the user. - for _, cp := range efw.ConfigPatches { - if cp.ApplyTo != networking.EnvoyFilter_VIRTUAL_HOST { - continue - } - - if cp.Operation != networking.EnvoyFilter_Patch_REMOVE { - continue - } - - // iterate through all virtual hosts in a route and remove ones that match - for i := range routeConfiguration.VirtualHosts { - if routeConfiguration.VirtualHosts[i].Name == "" { - // removed by another envoy filter - continue - } - if routeConfigurationMatch(routeConfiguration, &routeConfiguration.VirtualHosts[i], pluginParams, cp.Match) { - // set name to empty. We remove virtual hosts with empty names later in this function - routeConfiguration.VirtualHosts[i].Name = "" - virtualHostsRemoved = true - } - } - } - - for _, cp := range efw.ConfigPatches { - if cp.ApplyTo != networking.EnvoyFilter_ROUTE_CONFIGURATION && - cp.ApplyTo != networking.EnvoyFilter_VIRTUAL_HOST { - continue - } - - if cp.Operation != networking.EnvoyFilter_Patch_MERGE { - continue - } - - // if applying the merge at routeConfiguration level, then do standard proto merge - if cp.ApplyTo == networking.EnvoyFilter_ROUTE_CONFIGURATION { - if routeConfigurationMatch(routeConfiguration, nil, pluginParams, cp.Match) { - proto.Merge(routeConfiguration, cp.Value) - } - } else { - // This is for a specific virtual host. We have to iterate through all the vhosts in a route, - // and match the specific virtual host to merge - for i := range routeConfiguration.VirtualHosts { - if routeConfiguration.VirtualHosts[i].Name == "" { - // removed by another envoy filter - continue - } - - if routeConfigurationMatch(routeConfiguration, &routeConfiguration.VirtualHosts[i], pluginParams, cp.Match) { - proto.Merge(&routeConfiguration.VirtualHosts[i], cp.Value) - } - } - } - } - - // Add virtual host if the operation is add, and patch context matches - for _, cp := range efw.ConfigPatches { - if cp.ApplyTo != networking.EnvoyFilter_VIRTUAL_HOST { - continue - } - - if cp.Operation != networking.EnvoyFilter_Patch_ADD { - continue - } - - if routeConfigurationMatch(routeConfiguration, nil, pluginParams, cp.Match) { - routeConfiguration.VirtualHosts = append(routeConfiguration.VirtualHosts, *cp.Value.(*route.VirtualHost)) - } - } - } - if virtualHostsRemoved { - trimmedVirtualHosts := make([]route.VirtualHost, 0, len(routeConfiguration.VirtualHosts)) - for _, virtualHost := range routeConfiguration.VirtualHosts { - if virtualHost.Name == "" { - continue - } - trimmedVirtualHosts = append(trimmedVirtualHosts, virtualHost) - } - routeConfiguration.VirtualHosts = trimmedVirtualHosts - } - return routeConfiguration -} - -func clusterMatch(proxy *model.Proxy, cluster *xdsapi.Cluster, - matchCondition *networking.EnvoyFilter_EnvoyConfigObjectMatch, operation networking.EnvoyFilter_Patch_Operation) bool { - if matchCondition == nil { - return true - } - - getClusterContext := func(proxy *model.Proxy, cluster *xdsapi.Cluster) networking.EnvoyFilter_PatchContext { - if proxy.Type == model.Router { - return networking.EnvoyFilter_GATEWAY - } - if strings.HasPrefix(cluster.Name, string(model.TrafficDirectionInbound)) { - return networking.EnvoyFilter_SIDECAR_INBOUND - } - return networking.EnvoyFilter_SIDECAR_OUTBOUND - } - - patchContextToProxyType := func(context networking.EnvoyFilter_PatchContext) model.NodeType { - if context == networking.EnvoyFilter_GATEWAY { - return model.Router - } - return model.SidecarProxy - } - - // For cluster adds, cluster param will be nil. In this case, we simply have to match - // between gateways and sidecar contexts. No inbound/outbound - if operation == networking.EnvoyFilter_Patch_ADD { - if matchCondition.Context == networking.EnvoyFilter_ANY { - return true - } - - return patchContextToProxyType(matchCondition.Context) == proxy.Type - } - - // cluster is not nil. This is for merge and remove ops - if matchCondition.Context != networking.EnvoyFilter_ANY { - if matchCondition.Context != getClusterContext(proxy, cluster) { - return false - } - } - - cMatch := matchCondition.GetCluster() - if cMatch == nil { - return true - } - - if cMatch.Name != "" { - return cMatch.Name == cluster.Name - } - - _, subset, host, port := model.ParseSubsetKey(cluster.Name) - - if cMatch.Subset != "" && cMatch.Subset != subset { - return false - } - - if cMatch.Service != "" && config.Hostname(cMatch.Service) != host { - return false - } - - // FIXME: Ports on a cluster can be 0. the API only takes uint32 for ports - // We should either make that field in API as a wrapper type or switch to int - if cMatch.PortNumber != 0 && int(cMatch.PortNumber) != port { - return false - } - return true -} - -func listenerMatch(patchContext networking.EnvoyFilter_PatchContext, listener *xdsapi.Listener, cp *model.EnvoyFilterConfigPatchWrapper) bool { - if cp.Match == nil { - return true - } - - // For listener adds, listener param will be nil. In this case, we simply have to match - // between gateways and sidecar contexts. No inbound/outbound - if cp.Operation == networking.EnvoyFilter_Patch_ADD { - return cp.Match.Context == patchContext || cp.Match.Context == networking.EnvoyFilter_ANY - } - - // listener is not nil. This is for merge and remove ops - if cp.Match.Context != networking.EnvoyFilter_ANY { - if cp.Match.Context != patchContext { - return false - } - } - - cMatch := cp.Match.GetListener() - if cMatch == nil { - return true - } - - // FIXME: Ports on a listener can be 0. the API only takes uint32 for ports - // We should either make that field in API as a wrapper type or switch to int - if cMatch.PortNumber != 0 { - sockAddr := listener.Address.GetSocketAddress() - if sockAddr == nil || sockAddr.GetPortValue() != cMatch.PortNumber { - return false - } - } - - if cMatch.Name != "" && cMatch.Name != listener.Name { - return false - } - - if cp.ApplyTo == networking.EnvoyFilter_LISTENER { - // nothing more to match here. - return true - } - - matchFound := true - if cMatch.FilterChain != nil { - matchFound = false - for _, fc := range listener.FilterChains { - if filterChainMatch(cMatch.FilterChain, fc.FilterChainMatch) { - matchFound = true - if cMatch.FilterChain.Filter != nil { - matchFound = false - for _, networkFilter := range fc.Filters { - // validation ensures that filter name is not empty - if networkFilter.Name == cMatch.FilterChain.Filter.Name { - matchFound = true - // subfilter match is not necessary at it applies only to HTTP_FILTER - // while here, we are matching only for listener/network_filter or filter_chain - } - if matchFound { - break - } - } - } - } - if matchFound { - break - } - } - } - return matchFound -} - -func routeConfigurationMatch(rc *xdsapi.RouteConfiguration, vh *route.VirtualHost, pluginParams *plugin.InputParams, - matchCondition *networking.EnvoyFilter_EnvoyConfigObjectMatch) bool { - if matchCondition == nil { - return true - } - - if matchCondition.Context != networking.EnvoyFilter_ANY { - if matchCondition.Context != pluginParams.ListenerCategory { - return false - } - } - - cMatch := matchCondition.GetRouteConfiguration() - if cMatch == nil { - return true - } - - // we match on the port number and virtual host for sidecars - // we match on port number, server port name, gateway name, plus virtual host for gateways - if pluginParams.Node.Type == model.SidecarProxy { - // FIXME: Ports on a route can be 0. the API only takes uint32 for ports - // We should either make that field in API as a wrapper type or switch to int - if cMatch.PortNumber != 0 && int(cMatch.PortNumber) != pluginParams.Port.Port { - return false - } - - if cMatch.Name != "" && cMatch.Name != rc.Name { - return false - } - - // ports have matched for the rds in sidecar. Check for virtual host match if any - return virtualHostMatch(cMatch.Vhost, vh) - } - - // This is a gateway. Get all the fields in the gateway's RDS route name - portNumber, portName, gateway := model.ParseGatewayRDSRouteName(rc.Name) - if cMatch.PortNumber != 0 && int(cMatch.PortNumber) != portNumber { - return false - } - if cMatch.PortName != "" && cMatch.PortName != portName { - return false - } - if cMatch.Gateway != "" && cMatch.Gateway != gateway { - return false - } - - if cMatch.Name != "" && cMatch.Name != rc.Name { - return false - } - - // all gateway fields have matched for the rds. Check for virtual host match if any - return virtualHostMatch(cMatch.Vhost, vh) -} - -func virtualHostMatch(match *networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch, - vh *route.VirtualHost) bool { - if match == nil { - // match any virtual host in the named route configuration - return true - } - if vh == nil { - // route configuration has a specific match for a virtual host but - // we dont have a virtual host to match. - return false - } - // check if virtual host names match - return match.Name == vh.Name -} - -func filterChainMatch(match *networking.EnvoyFilter_ListenerMatch_FilterChainMatch, fc *listener.FilterChainMatch) bool { - if match == nil { - return true - } - if match.Sni != "" { - if fc == nil || len(fc.ServerNames) == 0 { - return false - } - sniMatched := false - for _, sni := range fc.ServerNames { - if sni == match.Sni { - sniMatched = true - break - } - } - if !sniMatched { - return false - } - } - - if match.TransportProtocol != "" { - if fc == nil || fc.TransportProtocol != match.TransportProtocol { - return false - } - } - return true -} diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch.go new file mode 100644 index 000000000000..7c43718d3a74 --- /dev/null +++ b/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch.go @@ -0,0 +1,104 @@ +// Copyright 2019 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 envoyfilter + +import ( + xdsapi "github.com/envoyproxy/go-control-plane/envoy/api/v2" + "github.com/gogo/protobuf/proto" + + networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config" +) + +// ApplyClusterPatches applies patches to CDS clusters +func ApplyClusterPatches(patchContext networking.EnvoyFilter_PatchContext, proxy *model.Proxy, + push *model.PushContext, clusters []*xdsapi.Cluster) []*xdsapi.Cluster { + + envoyFilterWrappers := push.EnvoyFilters(proxy) + clustersRemoved := false + for _, efw := range envoyFilterWrappers { + for _, cp := range efw.Patches[networking.EnvoyFilter_CLUSTER] { + if cp.Operation != networking.EnvoyFilter_Patch_REMOVE && + cp.Operation != networking.EnvoyFilter_Patch_MERGE { + continue + } + for i := range clusters { + if clusters[i] == nil { + // deleted by the remove operation + continue + } + + if patchContextMatch(patchContext, cp) && clusterMatch(clusters[i], cp) { + if cp.Operation == networking.EnvoyFilter_Patch_REMOVE { + clusters[i] = nil + clustersRemoved = true + } else { + proto.Merge(clusters[i], cp.Value) + } + } + } + } + + // Add cluster if the operation is add, and patch context matches + for _, cp := range efw.Patches[networking.EnvoyFilter_CLUSTER] { + if cp.Operation == networking.EnvoyFilter_Patch_ADD { + if patchContextMatch(patchContext, cp) { + clusters = append(clusters, cp.Value.(*xdsapi.Cluster)) + } + } + } + } + + if clustersRemoved { + trimmedClusters := make([]*xdsapi.Cluster, 0, len(clusters)) + for i := range clusters { + if clusters[i] == nil { + continue + } + trimmedClusters = append(trimmedClusters, clusters[i]) + } + clusters = trimmedClusters + } + return clusters +} + +func clusterMatch(cluster *xdsapi.Cluster, cp *model.EnvoyFilterConfigPatchWrapper) bool { + cMatch := cp.Match.GetCluster() + if cMatch == nil { + return true + } + + if cMatch.Name != "" { + return cMatch.Name == cluster.Name + } + + _, subset, host, port := model.ParseSubsetKey(cluster.Name) + + if cMatch.Subset != "" && cMatch.Subset != subset { + return false + } + + if cMatch.Service != "" && config.Hostname(cMatch.Service) != host { + return false + } + + // FIXME: Ports on a cluster can be 0. the API only takes uint32 for ports + // We should either make that field in API as a wrapper type or switch to int + if cMatch.PortNumber != 0 && int(cMatch.PortNumber) != port { + return false + } + return true +} diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch_test.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch_test.go new file mode 100644 index 000000000000..4ea1b6d59e1f --- /dev/null +++ b/pilot/pkg/networking/core/v1alpha3/envoyfilter/cluster_patch_test.go @@ -0,0 +1,297 @@ +// Copyright 2018 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 envoyfilter + +import ( + "testing" + + xdsapi "github.com/envoyproxy/go-control-plane/envoy/api/v2" + "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" + "github.com/google/go-cmp/cmp" + + networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pilot/pkg/networking/core/v1alpha3/fakes" +) + +func Test_clusterMatch(t *testing.T) { + type args struct { + proxy *model.Proxy + cluster *xdsapi.Cluster + matchCondition *networking.EnvoyFilter_EnvoyConfigObjectMatch + operation networking.EnvoyFilter_Patch_Operation + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "name mismatch", + args: args{ + proxy: &model.Proxy{Type: model.SidecarProxy}, + operation: networking.EnvoyFilter_Patch_MERGE, + matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ + Cluster: &networking.EnvoyFilter_ClusterMatch{Name: "scooby"}, + }, + }, + cluster: &xdsapi.Cluster{Name: "scrappy"}, + }, + want: false, + }, + { + name: "subset mismatch", + args: args{ + proxy: &model.Proxy{Type: model.SidecarProxy}, + operation: networking.EnvoyFilter_Patch_MERGE, + matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ + Cluster: &networking.EnvoyFilter_ClusterMatch{ + PortNumber: 80, + Service: "foo.bar", + Subset: "v1", + }, + }, + }, + cluster: &xdsapi.Cluster{Name: "outbound|80|v2|foo.bar"}, + }, + want: false, + }, + { + name: "service mismatch", + args: args{ + proxy: &model.Proxy{Type: model.SidecarProxy}, + operation: networking.EnvoyFilter_Patch_MERGE, + matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ + Cluster: &networking.EnvoyFilter_ClusterMatch{ + PortNumber: 80, + Service: "foo.bar", + Subset: "v1", + }, + }, + }, + cluster: &xdsapi.Cluster{Name: "outbound|80|v1|google.com"}, + }, + want: false, + }, + { + name: "port mismatch", + args: args{ + proxy: &model.Proxy{Type: model.SidecarProxy}, + operation: networking.EnvoyFilter_Patch_MERGE, + matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ + Cluster: &networking.EnvoyFilter_ClusterMatch{ + PortNumber: 80, + Service: "foo.bar", + Subset: "v1", + }, + }, + }, + cluster: &xdsapi.Cluster{Name: "outbound|90|v1|foo.bar"}, + }, + want: false, + }, + { + name: "full match", + args: args{ + proxy: &model.Proxy{Type: model.SidecarProxy}, + operation: networking.EnvoyFilter_Patch_MERGE, + matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ + Cluster: &networking.EnvoyFilter_ClusterMatch{ + PortNumber: 80, + Service: "foo.bar", + Subset: "v1", + }, + }, + }, + cluster: &xdsapi.Cluster{Name: "outbound|80|v1|foo.bar"}, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := clusterMatch(tt.args.cluster, &model.EnvoyFilterConfigPatchWrapper{Match: tt.args.matchCondition}); got != tt.want { + t.Errorf("clusterMatch() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestApplyClusterPatches(t *testing.T) { + configPatches := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ + { + ApplyTo: networking.EnvoyFilter_CLUSTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_SIDECAR_OUTBOUND, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_ADD, + Value: buildPatchStruct(`{"name":"new-cluster1"}`), + }, + }, + { + ApplyTo: networking.EnvoyFilter_CLUSTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_SIDECAR_OUTBOUND, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_ADD, + Value: buildPatchStruct(`{"name":"new-cluster2"}`), + }, + }, + { + ApplyTo: networking.EnvoyFilter_CLUSTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_GATEWAY, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ + Cluster: &networking.EnvoyFilter_ClusterMatch{ + Service: "gateway.com", + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{Operation: networking.EnvoyFilter_Patch_REMOVE}, + }, + { + ApplyTo: networking.EnvoyFilter_CLUSTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_SIDECAR_INBOUND, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ + Cluster: &networking.EnvoyFilter_ClusterMatch{ + PortNumber: 9999, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{Operation: networking.EnvoyFilter_Patch_REMOVE}, + }, + { + ApplyTo: networking.EnvoyFilter_CLUSTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_ANY, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_MERGE, + Value: buildPatchStruct(`{"dns_lookup_family":"V6_ONLY"}`), + }, + }, + { + ApplyTo: networking.EnvoyFilter_CLUSTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_ANY, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_MERGE, + Value: buildPatchStruct(`{"lb_policy":"RING_HASH"}`), + }, + }, + } + + sidecarOutboundIn := []*xdsapi.Cluster{ + {Name: "cluster1", DnsLookupFamily: xdsapi.Cluster_V4_ONLY, LbPolicy: xdsapi.Cluster_ROUND_ROBIN}, + {Name: "cluster2", + Http2ProtocolOptions: &core.Http2ProtocolOptions{ + AllowConnect: true, + AllowMetadata: true, + }, LbPolicy: xdsapi.Cluster_MAGLEV, + }, + } + + sidecarOutboundOut := []*xdsapi.Cluster{ + {Name: "cluster1", DnsLookupFamily: xdsapi.Cluster_V6_ONLY, LbPolicy: xdsapi.Cluster_RING_HASH}, + {Name: "cluster2", + Http2ProtocolOptions: &core.Http2ProtocolOptions{ + AllowConnect: true, + AllowMetadata: true, + }, LbPolicy: xdsapi.Cluster_RING_HASH, DnsLookupFamily: xdsapi.Cluster_V6_ONLY, + }, + {Name: "new-cluster1", DnsLookupFamily: xdsapi.Cluster_V6_ONLY, LbPolicy: xdsapi.Cluster_RING_HASH}, + {Name: "new-cluster2", DnsLookupFamily: xdsapi.Cluster_V6_ONLY, LbPolicy: xdsapi.Cluster_RING_HASH}, + } + + sidecarInboundIn := []*xdsapi.Cluster{ + {Name: "cluster1", DnsLookupFamily: xdsapi.Cluster_V4_ONLY, LbPolicy: xdsapi.Cluster_ROUND_ROBIN}, + {Name: "inbound|9999||mgmtCluster"}, + } + sidecarInboundOut := []*xdsapi.Cluster{ + {Name: "cluster1", DnsLookupFamily: xdsapi.Cluster_V6_ONLY, LbPolicy: xdsapi.Cluster_RING_HASH}, + } + + gatewayInput := []*xdsapi.Cluster{ + {Name: "cluster1", DnsLookupFamily: xdsapi.Cluster_V4_ONLY, LbPolicy: xdsapi.Cluster_ROUND_ROBIN}, + {Name: "cluster2", + Http2ProtocolOptions: &core.Http2ProtocolOptions{ + AllowConnect: true, + AllowMetadata: true, + }, LbPolicy: xdsapi.Cluster_MAGLEV, + }, + {Name: "outbound|443||gateway.com"}, + } + gatewayOutput := []*xdsapi.Cluster{ + {Name: "cluster1", DnsLookupFamily: xdsapi.Cluster_V6_ONLY, LbPolicy: xdsapi.Cluster_RING_HASH}, + {Name: "cluster2", + Http2ProtocolOptions: &core.Http2ProtocolOptions{ + AllowConnect: true, + AllowMetadata: true, + }, LbPolicy: xdsapi.Cluster_RING_HASH, DnsLookupFamily: xdsapi.Cluster_V6_ONLY, + }, + } + + testCases := []struct { + name string + input []*xdsapi.Cluster + proxy *model.Proxy + patchContext networking.EnvoyFilter_PatchContext + output []*xdsapi.Cluster + }{ + { + name: "sidecar outbound cluster patch", + input: sidecarOutboundIn, + proxy: &model.Proxy{Type: model.SidecarProxy, ConfigNamespace: "not-default"}, + patchContext: networking.EnvoyFilter_SIDECAR_OUTBOUND, + output: sidecarOutboundOut, + }, + { + name: "sidecar inbound cluster patch", + input: sidecarInboundIn, + patchContext: networking.EnvoyFilter_SIDECAR_INBOUND, + proxy: &model.Proxy{Type: model.SidecarProxy, ConfigNamespace: "not-default"}, + output: sidecarInboundOut, + }, + { + name: "gateway cds patch", + input: gatewayInput, + patchContext: networking.EnvoyFilter_GATEWAY, + proxy: &model.Proxy{Type: model.Router, ConfigNamespace: "not-default"}, + output: gatewayOutput, + }, + } + + serviceDiscovery := &fakes.ServiceDiscovery{} + env := newTestEnvironment(serviceDiscovery, testMesh, buildEnvoyFilterConfigStore(configPatches)) + push := model.NewPushContext() + push.InitContext(env) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := ApplyClusterPatches(tc.patchContext, tc.proxy, push, tc.input) + if diff := cmp.Diff(tc.output, got); diff != "" { + t.Errorf("ApplyClusterPatches(): %s mismatch (-want +got):\n%s", tc.name, diff) + } + }) + } +} diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter/deprecated.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter/deprecated.go new file mode 100644 index 000000000000..877e20bc1aac --- /dev/null +++ b/pilot/pkg/networking/core/v1alpha3/envoyfilter/deprecated.go @@ -0,0 +1,303 @@ +// Copyright 2019 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 envoyfilter + +import ( + "net" + "strings" + + xdsapi "github.com/envoyproxy/go-control-plane/envoy/api/v2" + "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" + xdslistener "github.com/envoyproxy/go-control-plane/envoy/api/v2/listener" + http_conn "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2" + xdsutil "github.com/envoyproxy/go-control-plane/pkg/util" + + networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pilot/pkg/networking/plugin" + "istio.io/istio/pilot/pkg/networking/util" + "istio.io/istio/pkg/config" + "istio.io/pkg/log" +) + +// DeprecatedInsertUserFilters inserts envoy filters from deprecated envoy filter config. +// We process EnvoyFilter CRDs after calling all plugins and building the listener with the required filter chains +// Having the entire filter chain is essential because users can specify one or more filters to be inserted +// before/after a filter or remove one or more filters. +// We use the plugin.InputParams as a convenience object to pass around parameters like proxy, proxyInstances, ports, +// etc., instead of having a long argument list +// If one or more filters are added to the HTTP connection manager, we will update the last filter in the listener +// filter chain (which is the http connection manager) with the updated object. +func DeprecatedInsertUserFilters(in *plugin.InputParams, listener *xdsapi.Listener, + httpConnectionManagers []*http_conn.HttpConnectionManager) error { //nolint: unparam + filterCRD := getUserFiltersForWorkload(in.Env, in.Node.WorkloadLabels) + if filterCRD == nil { + return nil + } + + listenerIPAddress := getListenerIPAddress(&listener.Address) + if listenerIPAddress == nil { + log.Warnf("Failed to parse IP Address from plugin listener") + } + + for _, f := range filterCRD.Filters { + if !deprecatedListenerMatch(in, listenerIPAddress, f.ListenerMatch) { + continue + } + // 4 cases of filter insertion + // http listener, http filter + // tcp listener, tcp filter + // http listener, tcp filter + // tcp listener, http filter -- invalid + + for cnum, lFilterChain := range listener.FilterChains { + if util.IsHTTPFilterChain(lFilterChain) { + // The listener match logic does not take into account the listener protocol + // because filter chains in a listener can have multiple protocols. + // for each filter chain, if the filter chain has a http connection manager, + // treat it as a http listener + // ListenerProtocol defaults to ALL. But if user specified listener protocol TCP, then + // skip this filter chain as its a HTTP filter chain + if f.ListenerMatch != nil && + !(f.ListenerMatch.ListenerProtocol == networking.EnvoyFilter_DeprecatedListenerMatch_ALL || + f.ListenerMatch.ListenerProtocol == networking.EnvoyFilter_DeprecatedListenerMatch_HTTP) { + continue + } + + // Now that the match condition is true, insert the filter if compatible + // http listener, http filter case + if f.FilterType == networking.EnvoyFilter_Filter_HTTP { + // Insert into http connection manager + deprecatedInsertHTTPFilter(listener.Name, &listener.FilterChains[cnum], httpConnectionManagers[cnum], f, util.IsXDSMarshalingToAnyEnabled(in.Node)) + } else { + // http listener, tcp filter + deprecatedInsertNetworkFilter(listener.Name, &listener.FilterChains[cnum], f) + } + } else { + // The listener match logic does not take into account the listener protocol + // because filter chains in a listener can have multiple protocols. + // for each filter chain, if the filter chain does not have a http connection manager, + // treat it as a tcp listener + // ListenerProtocol defaults to ALL. But if user specified listener protocol HTTP, then + // skip this filter chain as its a TCP filter chain + if f.ListenerMatch != nil && + !(f.ListenerMatch.ListenerProtocol == networking.EnvoyFilter_DeprecatedListenerMatch_ALL || + f.ListenerMatch.ListenerProtocol == networking.EnvoyFilter_DeprecatedListenerMatch_TCP) { + continue + } + + // treat both as insert network filter X into network filter chain. + // We cannot insert a HTTP in filter in network filter chain. + // Even HTTP connection manager is a network filter + if f.FilterType == networking.EnvoyFilter_Filter_HTTP { + log.Warnf("Ignoring filter %s. Cannot insert HTTP filter in network filter chain", + f.FilterName) + continue + } + deprecatedInsertNetworkFilter(listener.Name, &listener.FilterChains[cnum], f) + } + } + } + return nil +} + +// NOTE: There can be only one filter for a workload. If multiple filters are defined, the behavior +// is undefined. +func getUserFiltersForWorkload(env *model.Environment, labels config.LabelsCollection) *networking.EnvoyFilter { + f := env.EnvoyFilter(labels) + if f != nil { + return f.Spec.(*networking.EnvoyFilter) + } + return nil +} + +func getListenerIPAddress(address *core.Address) net.IP { + if address != nil && address.Address != nil { + switch t := address.Address.(type) { + case *core.Address_SocketAddress: + if t.SocketAddress != nil { + ip := "0.0.0.0" + if t.SocketAddress.Address != "::" { + ip = t.SocketAddress.Address + } + return net.ParseIP(ip) + } + } + } + return nil +} + +func deprecatedListenerMatch(in *plugin.InputParams, listenerIP net.IP, + matchCondition *networking.EnvoyFilter_DeprecatedListenerMatch) bool { + if matchCondition == nil { + return true + } + + // match by port first + if matchCondition.PortNumber > 0 && in.Port.Port != int(matchCondition.PortNumber) { + return false + } + + // match by port name prefix + if matchCondition.PortNamePrefix != "" { + if !strings.HasPrefix(strings.ToLower(in.Port.Name), strings.ToLower(matchCondition.PortNamePrefix)) { + return false + } + } + + // case ANY implies do not care about proxy type or direction + if matchCondition.ListenerType != networking.EnvoyFilter_DeprecatedListenerMatch_ANY { + // check if the current listener category matches with the user specified type + if matchCondition.ListenerType != in.DeprecatedListenerCategory { + return false + } + + // Check if the node's role matches properly with the listener category + switch matchCondition.ListenerType { + case networking.EnvoyFilter_DeprecatedListenerMatch_GATEWAY: + if in.Node.Type != model.Router { + return false // We don't care about direction for gateways + } + case networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_INBOUND, + networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND: + if in.Node.Type != model.SidecarProxy { + return false + } + } + } + + // Listener protocol will be matched as we try to insert the filters + + if len(matchCondition.Address) > 0 { + matched := false + // if any of the addresses here match, return true + for _, address := range matchCondition.Address { + if strings.Contains(address, "/") { + var ipNet *net.IPNet + var err error + if _, ipNet, err = net.ParseCIDR(address); err != nil { + log.Warnf("Failed to parse address %s in EnvoyFilter: %v", address, err) + continue + } + if ipNet.Contains(listenerIP) { + matched = true + break + } + } else if net.ParseIP(address).Equal(listenerIP) { + matched = true + break + } + } + return matched + } + + return true +} + +func deprecatedInsertHTTPFilter(listenerName string, filterChain *xdslistener.FilterChain, hcm *http_conn.HttpConnectionManager, + envoyFilter *networking.EnvoyFilter_Filter, isXDSMarshalingToAnyEnabled bool) { + filter := &http_conn.HttpFilter{ + Name: envoyFilter.FilterName, + ConfigType: &http_conn.HttpFilter_Config{Config: envoyFilter.FilterConfig}, + } + + position := networking.EnvoyFilter_InsertPosition_FIRST + if envoyFilter.InsertPosition != nil { + position = envoyFilter.InsertPosition.Index + } + + oldLen := len(hcm.HttpFilters) + switch position { + case networking.EnvoyFilter_InsertPosition_FIRST, networking.EnvoyFilter_InsertPosition_BEFORE: + hcm.HttpFilters = append([]*http_conn.HttpFilter{filter}, hcm.HttpFilters...) + if position == networking.EnvoyFilter_InsertPosition_BEFORE { + // bubble the filter to the right position scanning from beginning + for i := 1; i < len(hcm.HttpFilters); i++ { + if hcm.HttpFilters[i].Name != envoyFilter.InsertPosition.RelativeTo { + hcm.HttpFilters[i-1], hcm.HttpFilters[i] = hcm.HttpFilters[i], hcm.HttpFilters[i-1] + } else { + break + } + } + } + case networking.EnvoyFilter_InsertPosition_LAST, networking.EnvoyFilter_InsertPosition_AFTER: + hcm.HttpFilters = append(hcm.HttpFilters, filter) + if position == networking.EnvoyFilter_InsertPosition_AFTER { + // bubble the filter to the right position scanning from end + for i := len(hcm.HttpFilters) - 2; i >= 0; i-- { + if hcm.HttpFilters[i].Name != envoyFilter.InsertPosition.RelativeTo { + hcm.HttpFilters[i+1], hcm.HttpFilters[i] = hcm.HttpFilters[i], hcm.HttpFilters[i+1] + } else { + break + } + } + } + } + + // Rebuild the HTTP connection manager in the network filter chain + // Its the last filter in the filter chain + filterStruct := xdslistener.Filter{ + Name: xdsutil.HTTPConnectionManager, + } + if isXDSMarshalingToAnyEnabled { + filterStruct.ConfigType = &xdslistener.Filter_TypedConfig{TypedConfig: util.MessageToAny(hcm)} + } else { + filterStruct.ConfigType = &xdslistener.Filter_Config{Config: util.MessageToStruct(hcm)} + } + filterChain.Filters[len(filterChain.Filters)-1] = filterStruct + log.Infof("EnvoyFilters: Rebuilt HTTP Connection Manager %s (from %d filters to %d filters)", + listenerName, oldLen, len(hcm.HttpFilters)) +} + +func deprecatedInsertNetworkFilter(listenerName string, filterChain *xdslistener.FilterChain, + envoyFilter *networking.EnvoyFilter_Filter) { + filter := &xdslistener.Filter{ + Name: envoyFilter.FilterName, + ConfigType: &xdslistener.Filter_Config{Config: envoyFilter.FilterConfig}, + } + + position := networking.EnvoyFilter_InsertPosition_FIRST + if envoyFilter.InsertPosition != nil { + position = envoyFilter.InsertPosition.Index + } + + oldLen := len(filterChain.Filters) + switch position { + case networking.EnvoyFilter_InsertPosition_FIRST, networking.EnvoyFilter_InsertPosition_BEFORE: + filterChain.Filters = append([]xdslistener.Filter{*filter}, filterChain.Filters...) + if position == networking.EnvoyFilter_InsertPosition_BEFORE { + // bubble the filter to the right position scanning from beginning + for i := 1; i < len(filterChain.Filters); i++ { + if filterChain.Filters[i].Name != envoyFilter.InsertPosition.RelativeTo { + filterChain.Filters[i-1], filterChain.Filters[i] = filterChain.Filters[i], filterChain.Filters[i-1] + break + } + } + } + case networking.EnvoyFilter_InsertPosition_LAST, networking.EnvoyFilter_InsertPosition_AFTER: + filterChain.Filters = append(filterChain.Filters, *filter) + if position == networking.EnvoyFilter_InsertPosition_AFTER { + // bubble the filter to the right position scanning from end + for i := len(filterChain.Filters) - 2; i >= 0; i-- { + if filterChain.Filters[i].Name != envoyFilter.InsertPosition.RelativeTo { + filterChain.Filters[i+1], filterChain.Filters[i] = filterChain.Filters[i], filterChain.Filters[i+1] + break + } + } + } + } + log.Infof("EnvoyFilters: Rebuilt network filter stack for listener %s (from %d filters to %d filters)", + listenerName, oldLen, len(filterChain.Filters)) +} diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter/deprecated_test.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter/deprecated_test.go new file mode 100644 index 000000000000..d1a0f20f3012 --- /dev/null +++ b/pilot/pkg/networking/core/v1alpha3/envoyfilter/deprecated_test.go @@ -0,0 +1,159 @@ +// Copyright 2018 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 envoyfilter + +import ( + "net" + "testing" + + networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pilot/pkg/networking/plugin" +) + +func TestDeprecatedListenerMatch(t *testing.T) { + inputParams := &plugin.InputParams{ + ListenerProtocol: plugin.ListenerProtocolHTTP, + Node: &model.Proxy{ + Type: model.SidecarProxy, + }, + Port: &model.Port{ + Name: "http-foo", + Port: 80, + }, + } + + testCases := []struct { + name string + inputParams *plugin.InputParams + listenerIP net.IP + matchCondition *networking.EnvoyFilter_DeprecatedListenerMatch + direction networking.EnvoyFilter_DeprecatedListenerMatch_ListenerType + result bool + }{ + { + name: "empty match", + inputParams: inputParams, + result: true, + }, + { + name: "match by port", + inputParams: inputParams, + matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{PortNumber: 80}, + result: true, + }, + { + name: "match by port name prefix", + inputParams: inputParams, + matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{PortNamePrefix: "http"}, + result: true, + }, + { + name: "match by listener type", + inputParams: inputParams, + direction: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, + matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ListenerType: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND}, + result: true, + }, + { + name: "match by listener protocol", + inputParams: inputParams, + matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ListenerProtocol: networking.EnvoyFilter_DeprecatedListenerMatch_HTTP}, + result: true, + }, + { + name: "match by listener address with CIDR", + inputParams: inputParams, + listenerIP: net.ParseIP("10.10.10.10"), + matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{Address: []string{"10.10.10.10/24", "192.168.0.1/24"}}, + result: true, + }, + { + name: "match outbound sidecar http listeners on 10.10.10.0/24:80, with port name prefix http-*", + inputParams: inputParams, + listenerIP: net.ParseIP("10.10.10.10"), + direction: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, + matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ + PortNumber: 80, + PortNamePrefix: "http", + ListenerType: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, + ListenerProtocol: networking.EnvoyFilter_DeprecatedListenerMatch_HTTP, + Address: []string{"10.10.10.0/24"}, + }, + result: true, + }, + { + name: "does not match: outbound sidecar http listeners on 10.10.10.0/24:80, with port name prefix tcp-*", + inputParams: inputParams, + listenerIP: net.ParseIP("10.10.10.10"), + direction: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, + matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ + PortNumber: 80, + PortNamePrefix: "tcp", + ListenerType: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, + ListenerProtocol: networking.EnvoyFilter_DeprecatedListenerMatch_HTTP, + Address: []string{"10.10.10.0/24"}, + }, + result: false, + }, + { + name: "does not match: inbound sidecar http listeners with port name prefix http-*", + inputParams: inputParams, + direction: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, + matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ + PortNamePrefix: "http", + ListenerType: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_INBOUND, + ListenerProtocol: networking.EnvoyFilter_DeprecatedListenerMatch_HTTP, + }, + result: false, + }, + { + name: "does not match: outbound gateway http listeners on 10.10.10.0/24:80, with port name prefix http-*", + inputParams: inputParams, + listenerIP: net.ParseIP("10.10.10.10"), + direction: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, + matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ + PortNumber: 80, + PortNamePrefix: "http", + ListenerType: networking.EnvoyFilter_DeprecatedListenerMatch_GATEWAY, + ListenerProtocol: networking.EnvoyFilter_DeprecatedListenerMatch_HTTP, + Address: []string{"10.10.10.0/24"}, + }, + result: false, + }, + { + name: "does not match: outbound sidecar listeners on 172.16.0.1/16:80, with port name prefix http-*", + inputParams: inputParams, + listenerIP: net.ParseIP("10.10.10.10"), + direction: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, + matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ + PortNumber: 80, + PortNamePrefix: "http", + ListenerType: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, + ListenerProtocol: networking.EnvoyFilter_DeprecatedListenerMatch_HTTP, + Address: []string{"172.16.0.1/16"}, + }, + result: false, + }, + } + + for _, tc := range testCases { + tc.inputParams.DeprecatedListenerCategory = tc.direction + ret := deprecatedListenerMatch(tc.inputParams, tc.listenerIP, tc.matchCondition) + if tc.result != ret { + t.Errorf("%s: expecting %v but got %v", tc.name, tc.result, ret) + } + } +} diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch.go new file mode 100644 index 000000000000..1252be36354c --- /dev/null +++ b/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch.go @@ -0,0 +1,480 @@ +// Copyright 2019 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 envoyfilter + +import ( + xdsapi "github.com/envoyproxy/go-control-plane/envoy/api/v2" + xdslistener "github.com/envoyproxy/go-control-plane/envoy/api/v2/listener" + http_conn "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2" + xdsutil "github.com/envoyproxy/go-control-plane/pkg/util" + "github.com/gogo/protobuf/proto" + "github.com/gogo/protobuf/types" + + networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pilot/pkg/networking/util" +) + +// ApplyListenerPatches applies patches to LDS output +func ApplyListenerPatches(patchContext networking.EnvoyFilter_PatchContext, + proxy *model.Proxy, push *model.PushContext, listeners []*xdsapi.Listener, skipAdds bool) []*xdsapi.Listener { + + envoyFilterWrappers := push.EnvoyFilters(proxy) + return doListenerListOperation(patchContext, envoyFilterWrappers, listeners, skipAdds) +} + +func doListenerListOperation(patchContext networking.EnvoyFilter_PatchContext, + envoyFilterWrappers []*model.EnvoyFilterWrapper, + listeners []*xdsapi.Listener, skipAdds bool) []*xdsapi.Listener { + listenersRemoved := false + for _, efw := range envoyFilterWrappers { + // do all the changes for a single envoy filter crd object. [including adds] + // then move on to the next one + + // only removes/merges plus next level object operations [add/remove/merge] + for _, listener := range listeners { + if listener.Name == "" { + // removed by another op + continue + } + doListenerOperation(patchContext, efw.Patches, listener, &listenersRemoved) + } + // adds at listener level if enabled + if skipAdds { + continue + } + for _, cp := range efw.Patches[networking.EnvoyFilter_LISTENER] { + if cp.Operation == networking.EnvoyFilter_Patch_ADD { + if !patchContextMatch(patchContext, cp) { + continue + } + + listeners = append(listeners, cp.Value.(*xdsapi.Listener)) + } + } + } + if listenersRemoved { + tempArray := make([]*xdsapi.Listener, 0, len(listeners)) + for _, l := range listeners { + if l.Name != "" { + tempArray = append(tempArray, l) + } + } + return tempArray + } + return listeners +} + +func doListenerOperation(patchContext networking.EnvoyFilter_PatchContext, + patches map[networking.EnvoyFilter_ApplyTo][]*model.EnvoyFilterConfigPatchWrapper, + listener *xdsapi.Listener, listenersRemoved *bool) { + for _, cp := range patches[networking.EnvoyFilter_LISTENER] { + if !patchContextMatch(patchContext, cp) || + !listenerMatch(listener, cp) { + continue + } + + if cp.Operation == networking.EnvoyFilter_Patch_REMOVE { + listener.Name = "" + *listenersRemoved = true + // terminate the function here as we have nothing more do to for this listener + return + } else if cp.Operation == networking.EnvoyFilter_Patch_MERGE { + proto.Merge(listener, cp.Value) + } + } + + doFilterChainListOperation(patchContext, patches, listener) +} + +func doFilterChainListOperation(patchContext networking.EnvoyFilter_PatchContext, + patches map[networking.EnvoyFilter_ApplyTo][]*model.EnvoyFilterConfigPatchWrapper, + listener *xdsapi.Listener) { + filterChainsRemoved := false + for i, fc := range listener.FilterChains { + if fc.Filters == nil { + continue + } + doFilterChainOperation(patchContext, patches, listener, &listener.FilterChains[i], &filterChainsRemoved) + } + for _, cp := range patches[networking.EnvoyFilter_FILTER_CHAIN] { + if cp.Operation == networking.EnvoyFilter_Patch_ADD { + if !patchContextMatch(patchContext, cp) || + !listenerMatch(listener, cp) { + continue + } + listener.FilterChains = append(listener.FilterChains, *cp.Value.(*xdslistener.FilterChain)) + } + } + if filterChainsRemoved { + tempArray := make([]xdslistener.FilterChain, 0, len(listener.FilterChains)) + for _, fc := range listener.FilterChains { + if fc.Filters != nil { + tempArray = append(tempArray, fc) + } + } + listener.FilterChains = tempArray + } +} + +func doFilterChainOperation(patchContext networking.EnvoyFilter_PatchContext, + patches map[networking.EnvoyFilter_ApplyTo][]*model.EnvoyFilterConfigPatchWrapper, + listener *xdsapi.Listener, + fc *xdslistener.FilterChain, filterChainRemoved *bool) { + for _, cp := range patches[networking.EnvoyFilter_FILTER_CHAIN] { + if !patchContextMatch(patchContext, cp) || + !listenerMatch(listener, cp) || + !filterChainMatch(fc, cp) { + continue + } + if cp.Operation == networking.EnvoyFilter_Patch_REMOVE { + fc.Filters = nil + *filterChainRemoved = true + // nothing more to do in other patches as we removed this filter chain + return + } else if cp.Operation == networking.EnvoyFilter_Patch_MERGE { + proto.Merge(fc, cp.Value) + } + } + doNetworkFilterListOperation(patchContext, patches, listener, fc) +} + +func doNetworkFilterListOperation(patchContext networking.EnvoyFilter_PatchContext, + patches map[networking.EnvoyFilter_ApplyTo][]*model.EnvoyFilterConfigPatchWrapper, + listener *xdsapi.Listener, fc *xdslistener.FilterChain) { + networkFiltersRemoved := false + for i, filter := range fc.Filters { + if filter.Name == "" { + continue + } + doNetworkFilterOperation(patchContext, patches, listener, fc, &fc.Filters[i], &networkFiltersRemoved) + } + for _, cp := range patches[networking.EnvoyFilter_NETWORK_FILTER] { + if !patchContextMatch(patchContext, cp) || + !listenerMatch(listener, cp) || + !filterChainMatch(fc, cp) { + continue + } + + if cp.Operation == networking.EnvoyFilter_Patch_ADD { + fc.Filters = append(fc.Filters, *cp.Value.(*xdslistener.Filter)) + } else if cp.Operation == networking.EnvoyFilter_Patch_INSERT_AFTER { + // Insert after without a filter match is same as ADD in the end + if !hasNetworkFilterMatch(cp) { + fc.Filters = append(fc.Filters, *cp.Value.(*xdslistener.Filter)) + continue + } + // find the matching filter first + insertPosition := -1 + for i := 0; i < len(fc.Filters); i++ { + if networkFilterMatch(&fc.Filters[i], cp) { + insertPosition = i + 1 + break + } + } + + if insertPosition == -1 { + continue + } + + fc.Filters = append(fc.Filters, *cp.Value.(*xdslistener.Filter)) + if insertPosition < len(fc.Filters)-1 { + copy(fc.Filters[insertPosition+1:], fc.Filters[insertPosition:]) + fc.Filters[insertPosition] = *cp.Value.(*xdslistener.Filter) + } + } else if cp.Operation == networking.EnvoyFilter_Patch_INSERT_BEFORE { + // insert before without a filter match is same as insert in the beginning + if !hasNetworkFilterMatch(cp) { + fc.Filters = append([]xdslistener.Filter{*cp.Value.(*xdslistener.Filter)}, fc.Filters...) + continue + } + // find the matching filter first + insertPosition := -1 + for i := 0; i < len(fc.Filters); i++ { + if networkFilterMatch(&fc.Filters[i], cp) { + insertPosition = i + break + } + } + + if insertPosition == -1 { + continue + } + fc.Filters = append(fc.Filters, *cp.Value.(*xdslistener.Filter)) + copy(fc.Filters[insertPosition+1:], fc.Filters[insertPosition:]) + fc.Filters[insertPosition] = *cp.Value.(*xdslistener.Filter) + } + } + if networkFiltersRemoved { + tempArray := make([]xdslistener.Filter, 0, len(fc.Filters)) + for _, filter := range fc.Filters { + if filter.Name != "" { + tempArray = append(tempArray, filter) + } + } + fc.Filters = tempArray + } +} + +func doNetworkFilterOperation(patchContext networking.EnvoyFilter_PatchContext, + patches map[networking.EnvoyFilter_ApplyTo][]*model.EnvoyFilterConfigPatchWrapper, + listener *xdsapi.Listener, fc *xdslistener.FilterChain, + filter *xdslistener.Filter, networkFilterRemoved *bool) { + for _, cp := range patches[networking.EnvoyFilter_NETWORK_FILTER] { + if !patchContextMatch(patchContext, cp) || + !listenerMatch(listener, cp) || + !filterChainMatch(fc, cp) || + !networkFilterMatch(filter, cp) { + continue + } + if cp.Operation == networking.EnvoyFilter_Patch_REMOVE { + filter.Name = "" + *networkFilterRemoved = true + // nothing more to do in other patches as we removed this filter + return + } else if cp.Operation == networking.EnvoyFilter_Patch_MERGE { + proto.Merge(filter, cp.Value) + } + } + if filter.Name == xdsutil.HTTPConnectionManager { + doHTTPFilterListOperation(patchContext, patches, listener, fc, filter) + } +} + +func doHTTPFilterListOperation(patchContext networking.EnvoyFilter_PatchContext, + patches map[networking.EnvoyFilter_ApplyTo][]*model.EnvoyFilterConfigPatchWrapper, + listener *xdsapi.Listener, fc *xdslistener.FilterChain, filter *xdslistener.Filter) { + hcm := &http_conn.HttpConnectionManager{} + if filter.GetTypedConfig() != nil { + if err := types.UnmarshalAny(filter.GetTypedConfig(), hcm); err != nil { + return + // todo: figure out a non noisy logging option here + // as this loop will be called very frequently + } + } else { + if err := xdsutil.StructToMessage(filter.GetConfig(), hcm); err != nil { + return + } + } + httpFiltersRemoved := false + for _, httpFilter := range hcm.HttpFilters { + if httpFilter.Name == "" { + continue + } + doHTTPFilterOperation(patchContext, patches, listener, fc, filter, httpFilter, &httpFiltersRemoved) + } + for _, cp := range patches[networking.EnvoyFilter_HTTP_FILTER] { + if !patchContextMatch(patchContext, cp) || + !listenerMatch(listener, cp) || + !filterChainMatch(fc, cp) || + !networkFilterMatch(filter, cp) { + continue + } + + if cp.Operation == networking.EnvoyFilter_Patch_ADD { + hcm.HttpFilters = append(hcm.HttpFilters, cp.Value.(*http_conn.HttpFilter)) + } else if cp.Operation == networking.EnvoyFilter_Patch_INSERT_AFTER { + // Insert after without a filter match is same as ADD in the end + if !hasHTTPFilterMatch(cp) { + hcm.HttpFilters = append(hcm.HttpFilters, cp.Value.(*http_conn.HttpFilter)) + continue + } + + // find the matching filter first + insertPosition := -1 + for i := 0; i < len(hcm.HttpFilters); i++ { + if httpFilterMatch(hcm.HttpFilters[i], cp) { + insertPosition = i + 1 + break + } + } + + if insertPosition == -1 { + continue + } + + hcm.HttpFilters = append(hcm.HttpFilters, cp.Value.(*http_conn.HttpFilter)) + if insertPosition < len(hcm.HttpFilters)-1 { + copy(hcm.HttpFilters[insertPosition+1:], hcm.HttpFilters[insertPosition:]) + hcm.HttpFilters[insertPosition] = cp.Value.(*http_conn.HttpFilter) + } + } else if cp.Operation == networking.EnvoyFilter_Patch_INSERT_BEFORE { + // insert before without a filter match is same as insert in the beginning + if !hasHTTPFilterMatch(cp) { + hcm.HttpFilters = append([]*http_conn.HttpFilter{cp.Value.(*http_conn.HttpFilter)}, hcm.HttpFilters...) + continue + } + + // find the matching filter first + insertPosition := -1 + for i := 0; i < len(hcm.HttpFilters); i++ { + if httpFilterMatch(hcm.HttpFilters[i], cp) { + insertPosition = i + 1 + break + } + } + + if insertPosition == -1 { + continue + } + hcm.HttpFilters = append(hcm.HttpFilters, cp.Value.(*http_conn.HttpFilter)) + copy(hcm.HttpFilters[insertPosition+1:], hcm.HttpFilters[insertPosition:]) + hcm.HttpFilters[insertPosition] = cp.Value.(*http_conn.HttpFilter) + } + } + if httpFiltersRemoved { + tempArray := make([]*http_conn.HttpFilter, 0, len(hcm.HttpFilters)) + for _, filter := range hcm.HttpFilters { + if filter.Name != "" { + tempArray = append(tempArray, filter) + } + } + hcm.HttpFilters = tempArray + } + if filter.GetTypedConfig() != nil { + // convert to any type + filter.ConfigType = &xdslistener.Filter_TypedConfig{TypedConfig: util.MessageToAny(hcm)} + } else { + filter.ConfigType = &xdslistener.Filter_Config{Config: util.MessageToStruct(hcm)} + } +} + +func doHTTPFilterOperation(patchContext networking.EnvoyFilter_PatchContext, + patches map[networking.EnvoyFilter_ApplyTo][]*model.EnvoyFilterConfigPatchWrapper, + listener *xdsapi.Listener, fc *xdslistener.FilterChain, filter *xdslistener.Filter, + httpFilter *http_conn.HttpFilter, httpFilterRemoved *bool) { + for _, cp := range patches[networking.EnvoyFilter_HTTP_FILTER] { + if !patchContextMatch(patchContext, cp) || + !listenerMatch(listener, cp) || + !filterChainMatch(fc, cp) || + !networkFilterMatch(filter, cp) || + !httpFilterMatch(httpFilter, cp) { + continue + } + if cp.Operation == networking.EnvoyFilter_Patch_REMOVE { + httpFilter.Name = "" + *httpFilterRemoved = true + // nothing more to do in other patches as we removed this filter + return + } else if cp.Operation == networking.EnvoyFilter_Patch_MERGE { + proto.Merge(httpFilter, cp.Value) + } + } +} + +func listenerMatch(listener *xdsapi.Listener, cp *model.EnvoyFilterConfigPatchWrapper) bool { + cMatch := cp.Match.GetListener() + if cMatch == nil { + return true + } + + // FIXME: Ports on a listener can be 0. the API only takes uint32 for ports + // We should either make that field in API as a wrapper type or switch to int + if cMatch.PortNumber != 0 { + sockAddr := listener.Address.GetSocketAddress() + if sockAddr == nil || sockAddr.GetPortValue() != cMatch.PortNumber { + return false + } + } + + if cMatch.Name != "" && cMatch.Name != listener.Name { + return false + } + + return true +} + +// We assume that the parent listener has already been matched +func filterChainMatch(fc *xdslistener.FilterChain, cp *model.EnvoyFilterConfigPatchWrapper) bool { + cMatch := cp.Match.GetListener() + if cMatch == nil { + return true + } + + match := cMatch.FilterChain + if match == nil { + return true + } + if match.Sni != "" { + if fc.FilterChainMatch == nil || len(fc.FilterChainMatch.ServerNames) == 0 { + return false + } + sniMatched := false + for _, sni := range fc.FilterChainMatch.ServerNames { + if sni == match.Sni { + sniMatched = true + break + } + } + if !sniMatched { + return false + } + } + + if match.TransportProtocol != "" { + if fc.FilterChainMatch == nil || fc.FilterChainMatch.TransportProtocol != match.TransportProtocol { + return false + } + } + return true +} + +func hasNetworkFilterMatch(cp *model.EnvoyFilterConfigPatchWrapper) bool { + cMatch := cp.Match.GetListener() + if cMatch == nil { + return false + } + + fcMatch := cMatch.FilterChain + if fcMatch == nil { + return false + } + + return fcMatch.Filter != nil +} + +// We assume that the parent listener and filter chain have already been matched +func networkFilterMatch(filter *xdslistener.Filter, cp *model.EnvoyFilterConfigPatchWrapper) bool { + if !hasNetworkFilterMatch(cp) { + return true + } + + return cp.Match.GetListener().FilterChain.Filter.Name == filter.Name +} + +func hasHTTPFilterMatch(cp *model.EnvoyFilterConfigPatchWrapper) bool { + if !hasNetworkFilterMatch(cp) { + return false + } + + match := cp.Match.GetListener().FilterChain.Filter.SubFilter + return match != nil +} + +// We assume that the parent listener and filter chain, and network filter have already been matched +func httpFilterMatch(filter *http_conn.HttpFilter, cp *model.EnvoyFilterConfigPatchWrapper) bool { + if !hasHTTPFilterMatch(cp) { + return true + } + + match := cp.Match.GetListener().FilterChain.Filter.SubFilter + + return match.Name == filter.Name +} + +func patchContextMatch(patchContext networking.EnvoyFilter_PatchContext, + cp *model.EnvoyFilterConfigPatchWrapper) bool { + return cp.Match.Context == patchContext || cp.Match.Context == networking.EnvoyFilter_ANY +} diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch_test.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch_test.go new file mode 100644 index 000000000000..7dfaa1e70ba2 --- /dev/null +++ b/pilot/pkg/networking/core/v1alpha3/envoyfilter/listener_patch_test.go @@ -0,0 +1,635 @@ +// Copyright 2018 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 envoyfilter + +import ( + "fmt" + "strings" + "testing" + + xdsapi "github.com/envoyproxy/go-control-plane/envoy/api/v2" + "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth" + "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" + "github.com/envoyproxy/go-control-plane/envoy/api/v2/listener" + http_conn "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2" + xdsutil "github.com/envoyproxy/go-control-plane/pkg/util" + + "github.com/gogo/protobuf/jsonpb" + "github.com/gogo/protobuf/types" + "github.com/google/go-cmp/cmp" + + meshconfig "istio.io/api/mesh/v1alpha1" + networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pilot/pkg/networking/core/v1alpha3/fakes" + "istio.io/istio/pilot/pkg/networking/util" +) + +var ( + testMesh = meshconfig.MeshConfig{ + ConnectTimeout: &types.Duration{ + Seconds: 10, + Nanos: 1, + }, + } +) + +func buildEnvoyFilterConfigStore(configPatches []*networking.EnvoyFilter_EnvoyConfigObjectPatch) *fakes.IstioConfigStore { + return &fakes.IstioConfigStore{ + ListStub: func(typ, namespace string) (configs []model.Config, e error) { + if typ == "envoy-filter" { + // to emulate returning multiple envoy filter configs + for i, cp := range configPatches { + configs = append(configs, model.Config{ + ConfigMeta: model.ConfigMeta{ + Name: fmt.Sprintf("test-envoyfilter-%d", i), + Namespace: "not-default", + }, + Spec: &networking.EnvoyFilter{ + ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{cp}, + }, + }) + } + } + return + }, + } +} + +func buildPatchStruct(config string) *types.Struct { + val := &types.Struct{} + jsonpb.Unmarshal(strings.NewReader(config), val) + return val +} + +func newTestEnvironment(serviceDiscovery model.ServiceDiscovery, mesh meshconfig.MeshConfig, + configStore model.IstioConfigStore) *model.Environment { + env := &model.Environment{ + ServiceDiscovery: serviceDiscovery, + IstioConfigStore: configStore, + Mesh: &mesh, + } + + env.PushContext = model.NewPushContext() + _ = env.PushContext.InitContext(env) + + return env +} + +func TestApplyListenerPatches(t *testing.T) { + configPatches := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ + { + ApplyTo: networking.EnvoyFilter_LISTENER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_SIDECAR_OUTBOUND, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_ADD, + Value: buildPatchStruct(`{"name":"new-outbound-listener1"}`), + }, + }, + { + ApplyTo: networking.EnvoyFilter_NETWORK_FILTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_SIDECAR_OUTBOUND, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networking.EnvoyFilter_ListenerMatch{ + PortNumber: 12345, + FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ + Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{Name: "filter1"}, + }, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE, + Value: buildPatchStruct(`{"name":"filter0"}`), + }, + }, + { + ApplyTo: networking.EnvoyFilter_NETWORK_FILTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_SIDECAR_OUTBOUND, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networking.EnvoyFilter_ListenerMatch{ + PortNumber: 12345, + FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ + Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{Name: "filter2"}, + }, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_REMOVE, + }, + }, + { + ApplyTo: networking.EnvoyFilter_LISTENER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_SIDECAR_INBOUND, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networking.EnvoyFilter_ListenerMatch{ + PortNumber: 12345, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_REMOVE, + }, + }, + { + ApplyTo: networking.EnvoyFilter_LISTENER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_SIDECAR_INBOUND, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networking.EnvoyFilter_ListenerMatch{ + PortNumber: 80, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_MERGE, + Value: buildPatchStruct(`{listener_filters: nil}`), + }, + }, + { + ApplyTo: networking.EnvoyFilter_FILTER_CHAIN, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_SIDECAR_INBOUND, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networking.EnvoyFilter_ListenerMatch{ + PortNumber: 80, + FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{TransportProtocol: "tls"}, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_REMOVE, + }, + }, + { + ApplyTo: networking.EnvoyFilter_LISTENER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_GATEWAY, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networking.EnvoyFilter_ListenerMatch{ + PortNumber: 80, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_MERGE, + Value: buildPatchStruct(`{"listener_filters": [{"name":"foo"}]}`), + }, + }, + { + ApplyTo: networking.EnvoyFilter_FILTER_CHAIN, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_GATEWAY, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networking.EnvoyFilter_ListenerMatch{ + PortNumber: 80, + FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ + Sni: "*.foo.com", + }, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_MERGE, + Value: buildPatchStruct(`{"filter_chain_match": { "server_names": ["foo.com"] }}`), + }, + }, + { + ApplyTo: networking.EnvoyFilter_HTTP_FILTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_GATEWAY, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networking.EnvoyFilter_ListenerMatch{ + PortNumber: 80, + FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ + Sni: "*.foo.com", + Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{ + Name: xdsutil.HTTPConnectionManager, + SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{Name: "http-filter2"}, + }, + }, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_INSERT_AFTER, + Value: buildPatchStruct(`{"name": "http-filter3"}`), + }, + }, + { + ApplyTo: networking.EnvoyFilter_HTTP_FILTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_SIDECAR_INBOUND, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networking.EnvoyFilter_ListenerMatch{ + PortNumber: 80, + FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ + Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{ + Name: xdsutil.HTTPConnectionManager, + }, + }, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE, + Value: buildPatchStruct(`{"name": "http-filter3"}`), + }, + }, + } + + sidecarOutboundIn := []*xdsapi.Listener{ + { + Name: "12345", + Address: core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: 12345, + }, + }, + }, + }, + FilterChains: []listener.FilterChain{ + { + Filters: []listener.Filter{ + {Name: "filter1"}, + {Name: "filter2"}, + }, + }, + }, + }, + { + Name: "another-listener", + }, + } + + sidecarOutboundOut := []*xdsapi.Listener{ + { + Name: "12345", + Address: core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: 12345, + }, + }, + }, + }, + FilterChains: []listener.FilterChain{ + { + Filters: []listener.Filter{ + {Name: "filter0"}, + {Name: "filter1"}, + }, + }, + }, + }, + { + Name: "another-listener", + }, + { + Name: "new-outbound-listener1", + }, + } + + sidecarOutboundInNoAdd := []*xdsapi.Listener{ + { + Name: "12345", + Address: core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: 12345, + }, + }, + }, + }, + FilterChains: []listener.FilterChain{ + { + Filters: []listener.Filter{ + {Name: "filter1"}, + {Name: "filter2"}, + }, + }, + }, + }, + { + Name: "another-listener", + }, + } + + sidecarOutboundOutNoAdd := []*xdsapi.Listener{ + { + Name: "12345", + Address: core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: 12345, + }, + }, + }, + }, + FilterChains: []listener.FilterChain{ + { + Filters: []listener.Filter{ + {Name: "filter0"}, + {Name: "filter1"}, + }, + }, + }, + }, + { + Name: "another-listener", + }, + } + + sidecarInboundIn := []*xdsapi.Listener{ + { + Name: "12345", + Address: core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: 12345, + }, + }, + }, + }, + }, + { + Name: "another-listener", + Address: core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: 80, + }, + }, + }, + }, + ListenerFilters: []listener.ListenerFilter{{Name: "envoy.tls_inspector"}}, + FilterChains: []listener.FilterChain{ + { + FilterChainMatch: &listener.FilterChainMatch{TransportProtocol: "tls"}, + TlsContext: &auth.DownstreamTlsContext{}, + Filters: []listener.Filter{{Name: "network-filter"}}, + }, + { + Filters: []listener.Filter{ + { + Name: xdsutil.HTTPConnectionManager, + ConfigType: &listener.Filter_TypedConfig{ + TypedConfig: util.MessageToAny(&http_conn.HttpConnectionManager{ + HttpFilters: []*http_conn.HttpFilter{ + {Name: "http-filter1"}, + {Name: "http-filter2"}, + }, + }), + }, + }, + }, + }, + }, + }, + } + + sidecarInboundOut := []*xdsapi.Listener{ + { + Name: "another-listener", + Address: core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: 80, + }, + }, + }, + }, + ListenerFilters: []listener.ListenerFilter{{Name: "envoy.tls_inspector"}}, + FilterChains: []listener.FilterChain{ + { + Filters: []listener.Filter{ + { + Name: xdsutil.HTTPConnectionManager, + ConfigType: &listener.Filter_TypedConfig{ + TypedConfig: util.MessageToAny(&http_conn.HttpConnectionManager{ + HttpFilters: []*http_conn.HttpFilter{ + {Name: "http-filter3"}, + {Name: "http-filter1"}, + {Name: "http-filter2"}, + }, + }), + }, + }, + }, + }, + }, + }, + } + + gatewayIn := []*xdsapi.Listener{ + { + Name: "80", + Address: core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: 80, + }, + }, + }, + }, + FilterChains: []listener.FilterChain{ + { + FilterChainMatch: &listener.FilterChainMatch{ + ServerNames: []string{"match.com", "*.foo.com"}, + }, + Filters: []listener.Filter{ + { + Name: xdsutil.HTTPConnectionManager, + ConfigType: &listener.Filter_TypedConfig{ + TypedConfig: util.MessageToAny(&http_conn.HttpConnectionManager{ + HttpFilters: []*http_conn.HttpFilter{ + {Name: "http-filter1"}, + {Name: "http-filter2"}, + }, + }), + }, + }, + }, + }, + }, + }, + { + Name: "another-listener", + Address: core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: 443, + }, + }, + }, + }, + FilterChains: []listener.FilterChain{ + { + FilterChainMatch: &listener.FilterChainMatch{ + ServerNames: []string{"nomatch.com", "*.foo.com"}, + }, + Filters: []listener.Filter{{Name: "network-filter"}}, + }, + }, + }, + } + + gatewayOut := []*xdsapi.Listener{ + { + Name: "80", + Address: core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: 80, + }, + }, + }, + }, + ListenerFilters: []listener.ListenerFilter{{Name: "foo"}}, + FilterChains: []listener.FilterChain{ + { + FilterChainMatch: &listener.FilterChainMatch{ + ServerNames: []string{"match.com", "*.foo.com", "foo.com"}, + }, + Filters: []listener.Filter{ + { + Name: xdsutil.HTTPConnectionManager, + ConfigType: &listener.Filter_TypedConfig{ + TypedConfig: util.MessageToAny(&http_conn.HttpConnectionManager{ + HttpFilters: []*http_conn.HttpFilter{ + {Name: "http-filter1"}, + {Name: "http-filter2"}, + {Name: "http-filter3"}, + }, + }), + }, + }, + }, + }, + }, + }, + { + Name: "another-listener", + Address: core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: 443, + }, + }, + }, + }, + FilterChains: []listener.FilterChain{ + { + FilterChainMatch: &listener.FilterChainMatch{ + ServerNames: []string{"nomatch.com", "*.foo.com"}, + }, + Filters: []listener.Filter{{Name: "network-filter"}}, + }, + }, + }, + } + + sidecarProxy := &model.Proxy{Type: model.SidecarProxy, ConfigNamespace: "not-default"} + gatewayProxy := &model.Proxy{Type: model.Router, ConfigNamespace: "not-default"} + serviceDiscovery := &fakes.ServiceDiscovery{} + env := newTestEnvironment(serviceDiscovery, testMesh, buildEnvoyFilterConfigStore(configPatches)) + push := model.NewPushContext() + push.InitContext(env) + + type args struct { + patchContext networking.EnvoyFilter_PatchContext + proxy *model.Proxy + push *model.PushContext + listeners []*xdsapi.Listener + skipAdds bool + } + tests := []struct { + name string + args args + want []*xdsapi.Listener + }{ + { + name: "sidecar inbound lds", + args: args{ + patchContext: networking.EnvoyFilter_SIDECAR_INBOUND, + proxy: sidecarProxy, + push: push, + listeners: sidecarInboundIn, + skipAdds: false, + }, + want: sidecarInboundOut, + }, + { + name: "gateway lds", + args: args{ + patchContext: networking.EnvoyFilter_GATEWAY, + proxy: gatewayProxy, + push: push, + listeners: gatewayIn, + skipAdds: false, + }, + want: gatewayOut, + }, + { + name: "sidecar outbound lds", + args: args{ + patchContext: networking.EnvoyFilter_SIDECAR_OUTBOUND, + proxy: sidecarProxy, + push: push, + listeners: sidecarOutboundIn, + skipAdds: false, + }, + want: sidecarOutboundOut, + }, + { + name: "sidecar outbound lds - skip adds", + args: args{ + patchContext: networking.EnvoyFilter_SIDECAR_OUTBOUND, + proxy: sidecarProxy, + push: push, + listeners: sidecarOutboundInNoAdd, + skipAdds: true, + }, + want: sidecarOutboundOutNoAdd, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ApplyListenerPatches(tt.args.patchContext, tt.args.proxy, tt.args.push, + tt.args.listeners, tt.args.skipAdds) + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("ApplyListenerPatches(): %s mismatch (-want +got):\n%s", tt.name, diff) + } + }) + } +} diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter/rc_patch.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter/rc_patch.go new file mode 100644 index 000000000000..27b70793a972 --- /dev/null +++ b/pilot/pkg/networking/core/v1alpha3/envoyfilter/rc_patch.go @@ -0,0 +1,168 @@ +// Copyright 2019 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 envoyfilter + +import ( + "strconv" + "strings" + + xdsapi "github.com/envoyproxy/go-control-plane/envoy/api/v2" + "github.com/envoyproxy/go-control-plane/envoy/api/v2/route" + "github.com/gogo/protobuf/proto" + + networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/model" +) + +func ApplyRouteConfigurationPatches(patchContext networking.EnvoyFilter_PatchContext, + proxy *model.Proxy, push *model.PushContext, + routeConfiguration *xdsapi.RouteConfiguration) *xdsapi.RouteConfiguration { + + virtualHostsRemoved := false + envoyFilterWrappers := push.EnvoyFilters(proxy) + for _, efw := range envoyFilterWrappers { + // only merge is applicable for route configuration. Validation checks for the same. + for _, cp := range efw.Patches[networking.EnvoyFilter_ROUTE_CONFIGURATION] { + if patchContextMatch(patchContext, cp) && + routeConfigurationMatch(patchContext, routeConfiguration, cp) { + proto.Merge(routeConfiguration, cp.Value) + } + } + + // First process remove operations, then the merge and finally the add. + // If add is done before remove, then remove could end up deleting a vhost that + // was added by the user. + for _, cp := range efw.Patches[networking.EnvoyFilter_VIRTUAL_HOST] { + if cp.Operation != networking.EnvoyFilter_Patch_REMOVE && + cp.Operation != networking.EnvoyFilter_Patch_MERGE { + continue + } + + if !patchContextMatch(patchContext, cp) || + !routeConfigurationMatch(patchContext, routeConfiguration, cp) { + continue + } + + // iterate through all virtual hosts in a route and remove/merge ones that match + for i := range routeConfiguration.VirtualHosts { + if routeConfiguration.VirtualHosts[i].Name == "" { + // removed by another envoy filter + continue + } + if virtualHostMatch(&routeConfiguration.VirtualHosts[i], cp) { + if cp.Operation == networking.EnvoyFilter_Patch_REMOVE { + // set name to empty. We remove virtual hosts with empty names later in this function + routeConfiguration.VirtualHosts[i].Name = "" + virtualHostsRemoved = true + } else { + proto.Merge(&routeConfiguration.VirtualHosts[i], cp.Value) + } + } + } + } + + // Add virtual host if the operation is add, and patch context matches + for _, cp := range efw.Patches[networking.EnvoyFilter_VIRTUAL_HOST] { + if cp.Operation != networking.EnvoyFilter_Patch_ADD { + continue + } + + if patchContextMatch(patchContext, cp) && + routeConfigurationMatch(patchContext, routeConfiguration, cp) { + routeConfiguration.VirtualHosts = append(routeConfiguration.VirtualHosts, *cp.Value.(*route.VirtualHost)) + } + } + } + if virtualHostsRemoved { + trimmedVirtualHosts := make([]route.VirtualHost, 0, len(routeConfiguration.VirtualHosts)) + for _, virtualHost := range routeConfiguration.VirtualHosts { + if virtualHost.Name == "" { + continue + } + trimmedVirtualHosts = append(trimmedVirtualHosts, virtualHost) + } + routeConfiguration.VirtualHosts = trimmedVirtualHosts + } + return routeConfiguration +} + +func routeConfigurationMatch(patchContext networking.EnvoyFilter_PatchContext, rc *xdsapi.RouteConfiguration, + cp *model.EnvoyFilterConfigPatchWrapper) bool { + cMatch := cp.Match.GetRouteConfiguration() + if cMatch == nil { + return true + } + + // we match on the port number and virtual host for sidecars + // we match on port number, server port name, gateway name, plus virtual host for gateways + if patchContext != networking.EnvoyFilter_GATEWAY { + listenerPort := 0 + if strings.HasPrefix(rc.Name, string(model.TrafficDirectionInbound)) { + _, _, _, listenerPort = model.ParseSubsetKey(rc.Name) + } else { + listenerPort, _ = strconv.Atoi(rc.Name) + } + + // FIXME: Ports on a route can be 0. the API only takes uint32 for ports + // We should either make that field in API as a wrapper type or switch to int + if cMatch.PortNumber != 0 && int(cMatch.PortNumber) != listenerPort { + return false + } + + if cMatch.Name != "" && cMatch.Name != rc.Name { + return false + } + + return true + } + + // This is a gateway. Get all the fields in the gateway's RDS route name + portNumber, portName, gateway := model.ParseGatewayRDSRouteName(rc.Name) + if cMatch.PortNumber != 0 && int(cMatch.PortNumber) != portNumber { + return false + } + if cMatch.PortName != "" && cMatch.PortName != portName { + return false + } + if cMatch.Gateway != "" && cMatch.Gateway != gateway { + return false + } + + if cMatch.Name != "" && cMatch.Name != rc.Name { + return false + } + + return true +} + +func virtualHostMatch(vh *route.VirtualHost, cp *model.EnvoyFilterConfigPatchWrapper) bool { + cMatch := cp.Match.GetRouteConfiguration() + if cMatch == nil { + return true + } + + match := cMatch.Vhost + if match == nil { + // match any virtual host in the named route configuration + return true + } + if vh == nil { + // route configuration has a specific match for a virtual host but + // we dont have a virtual host to match. + return false + } + // check if virtual host names match + return match.Name == vh.Name +} diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter/rc_patch_test.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter/rc_patch_test.go new file mode 100644 index 000000000000..65d0c9309dbc --- /dev/null +++ b/pilot/pkg/networking/core/v1alpha3/envoyfilter/rc_patch_test.go @@ -0,0 +1,411 @@ +// Copyright 2018 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 envoyfilter + +import ( + "testing" + + xdsapi "github.com/envoyproxy/go-control-plane/envoy/api/v2" + "github.com/envoyproxy/go-control-plane/envoy/api/v2/route" + "github.com/google/go-cmp/cmp" + + networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pilot/pkg/networking/core/v1alpha3/fakes" +) + +func Test_virtualHostMatch(t *testing.T) { + type args struct { + cp *model.EnvoyFilterConfigPatchWrapper + vh *route.VirtualHost + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "vh is nil", + args: args{ + cp: &model.EnvoyFilterConfigPatchWrapper{ + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ + RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ + Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{}, + }, + }, + }, + }, + }, + want: false, + }, + { + name: "full match", + args: args{ + vh: &route.VirtualHost{ + Name: "scooby", + }, + cp: &model.EnvoyFilterConfigPatchWrapper{ + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ + RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ + Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ + Name: "scooby", + }, + }, + }, + }, + }, + }, + want: true, + }, + { + name: "mismatch", + args: args{ + vh: &route.VirtualHost{ + Name: "scoobydoo", + }, + cp: &model.EnvoyFilterConfigPatchWrapper{ + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ + RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ + Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ + Name: "scooby", + }, + }, + }, + }, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := virtualHostMatch(tt.args.vh, tt.args.cp); got != tt.want { + t.Errorf("virtualHostMatch() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_routeConfigurationMatch(t *testing.T) { + type args struct { + rc *xdsapi.RouteConfiguration + patchContext networking.EnvoyFilter_PatchContext + cp *model.EnvoyFilterConfigPatchWrapper + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "nil route match", + args: args{ + patchContext: networking.EnvoyFilter_SIDECAR_OUTBOUND, + cp: &model.EnvoyFilterConfigPatchWrapper{ + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{}, + }, + }, + want: true, + }, + { + name: "rc name mismatch", + args: args{ + patchContext: networking.EnvoyFilter_SIDECAR_OUTBOUND, + cp: &model.EnvoyFilterConfigPatchWrapper{ + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ + RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{Name: "scooby.80"}, + }, + }, + }, + rc: &xdsapi.RouteConfiguration{Name: "scooby.90"}, + }, + want: false, + }, + { + name: "sidecar port match", + args: args{ + patchContext: networking.EnvoyFilter_SIDECAR_OUTBOUND, + cp: &model.EnvoyFilterConfigPatchWrapper{ + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ + RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{PortNumber: 80}, + }, + }, + }, + rc: &xdsapi.RouteConfiguration{Name: "80"}, + }, + want: true, + }, + { + name: "sidecar port mismatch", + args: args{ + patchContext: networking.EnvoyFilter_SIDECAR_OUTBOUND, + cp: &model.EnvoyFilterConfigPatchWrapper{ + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ + RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{PortNumber: 80}, + }, + }, + }, + rc: &xdsapi.RouteConfiguration{Name: "90"}, + }, + want: false, + }, + { + name: "gateway fields match", + args: args{ + patchContext: networking.EnvoyFilter_GATEWAY, + cp: &model.EnvoyFilterConfigPatchWrapper{ + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ + RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ + PortNumber: 443, + PortName: "app1", + Gateway: "ns1/gw1", + }, + }, + }, + }, + rc: &xdsapi.RouteConfiguration{Name: "https.443.app1.gw1.ns1"}, + }, + want: true, + }, + { + name: "gateway fields mismatch", + args: args{ + patchContext: networking.EnvoyFilter_GATEWAY, + cp: &model.EnvoyFilterConfigPatchWrapper{ + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ + RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ + PortNumber: 443, + PortName: "app1", + Gateway: "ns1/gw1", + }, + }, + }, + }, + rc: &xdsapi.RouteConfiguration{Name: "http.80"}, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := routeConfigurationMatch(tt.args.patchContext, tt.args.rc, tt.args.cp); got != tt.want { + t.Errorf("routeConfigurationMatch() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestApplyRouteConfigurationPatches(t *testing.T) { + configPatches := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ + { + ApplyTo: networking.EnvoyFilter_ROUTE_CONFIGURATION, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_SIDECAR_OUTBOUND, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ + RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ + PortNumber: 80, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_MERGE, + Value: buildPatchStruct(`{"request_headers_to_remove":["h3", "h4"]}`), + }, + }, + { + ApplyTo: networking.EnvoyFilter_VIRTUAL_HOST, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_ADD, + Value: buildPatchStruct(`{"name":"new-vhost"}`), + }, + }, + { + ApplyTo: networking.EnvoyFilter_VIRTUAL_HOST, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_GATEWAY, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ + RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ + Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ + Name: "vhost1", + }, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{Operation: networking.EnvoyFilter_Patch_REMOVE}, + }, + { + ApplyTo: networking.EnvoyFilter_VIRTUAL_HOST, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_SIDECAR_INBOUND, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ + RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ + Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ + Name: "vhost2", + }, + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{Operation: networking.EnvoyFilter_Patch_REMOVE}, + }, + { + ApplyTo: networking.EnvoyFilter_VIRTUAL_HOST, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_ANY, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_MERGE, + Value: buildPatchStruct(`{"domains":["domain:80"]}`), + }, + }, + } + + sidecarOutboundRC := &xdsapi.RouteConfiguration{ + Name: "80", + VirtualHosts: []route.VirtualHost{ + { + Name: "foo.com", + Domains: []string{"domain"}, + }, + }, + RequestHeadersToRemove: []string{"h1", "h2"}, + } + patchedSidecarOutputRC := &xdsapi.RouteConfiguration{ + Name: "80", + VirtualHosts: []route.VirtualHost{ + { + Name: "foo.com", + Domains: []string{"domain", "domain:80"}, + }, + { + Name: "new-vhost", + Domains: []string{"domain:80"}, + }, + }, + RequestHeadersToRemove: []string{"h1", "h2", "h3", "h4"}, + } + sidecarInboundRC := &xdsapi.RouteConfiguration{ + Name: "inbound|http|80", + VirtualHosts: []route.VirtualHost{ + { + Name: "vhost2", + Domains: []string{"domain"}, + }, + }, + } + patchedSidecarInboundRC := &xdsapi.RouteConfiguration{ + Name: "inbound|http|80", + VirtualHosts: []route.VirtualHost{ + { + Name: "new-vhost", + Domains: []string{"domain:80"}, + }, + }, + } + + gatewayRC := &xdsapi.RouteConfiguration{ + Name: "80", + VirtualHosts: []route.VirtualHost{ + { + Name: "vhost1", + Domains: []string{"domain"}, + }, + { + Name: "gateway", + Domains: []string{"gateway"}, + }, + }, + } + patchedGatewayRC := &xdsapi.RouteConfiguration{ + Name: "80", + VirtualHosts: []route.VirtualHost{ + { + Name: "gateway", + Domains: []string{"gateway", "domain:80"}, + }, + { + Name: "new-vhost", + Domains: []string{"domain:80"}, + }, + }, + } + + serviceDiscovery := &fakes.ServiceDiscovery{} + env := newTestEnvironment(serviceDiscovery, testMesh, buildEnvoyFilterConfigStore(configPatches)) + push := model.NewPushContext() + push.InitContext(env) + + sidecarNode := &model.Proxy{Type: model.SidecarProxy, ConfigNamespace: "not-default"} + gatewayNode := &model.Proxy{Type: model.Router, ConfigNamespace: "not-default"} + + type args struct { + patchContext networking.EnvoyFilter_PatchContext + proxy *model.Proxy + push *model.PushContext + routeConfiguration *xdsapi.RouteConfiguration + } + tests := []struct { + name string + args args + want *xdsapi.RouteConfiguration + }{ + { + name: "sidecar outbound rds patch", + args: args{ + patchContext: networking.EnvoyFilter_SIDECAR_OUTBOUND, + proxy: sidecarNode, + push: push, + routeConfiguration: sidecarOutboundRC, + }, + want: patchedSidecarOutputRC, + }, + { + name: "sidecar inbound rc patch", + args: args{ + patchContext: networking.EnvoyFilter_SIDECAR_INBOUND, + proxy: sidecarNode, + push: push, + routeConfiguration: sidecarInboundRC, + }, + want: patchedSidecarInboundRC, + }, + { + name: "gateway rds patch", + args: args{ + patchContext: networking.EnvoyFilter_GATEWAY, + proxy: gatewayNode, + push: push, + routeConfiguration: gatewayRC, + }, + want: patchedGatewayRC, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ApplyRouteConfigurationPatches(tt.args.patchContext, tt.args.proxy, + tt.args.push, tt.args.routeConfiguration) + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("ApplyListenerPatches(): %s mismatch (-want +got):\n%s", tt.name, diff) + } + }) + } +} diff --git a/pilot/pkg/networking/core/v1alpha3/envoyfilter_test.go b/pilot/pkg/networking/core/v1alpha3/envoyfilter_test.go deleted file mode 100644 index 4b04b5adc2b5..000000000000 --- a/pilot/pkg/networking/core/v1alpha3/envoyfilter_test.go +++ /dev/null @@ -1,973 +0,0 @@ -// Copyright 2018 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 v1alpha3 - -import ( - "net" - "reflect" - "strings" - "testing" - - xdsapi "github.com/envoyproxy/go-control-plane/envoy/api/v2" - "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" - "github.com/envoyproxy/go-control-plane/envoy/api/v2/route" - "github.com/gogo/protobuf/jsonpb" - "github.com/gogo/protobuf/types" - - networking "istio.io/api/networking/v1alpha3" - "istio.io/istio/pilot/pkg/model" - "istio.io/istio/pilot/pkg/networking/core/v1alpha3/fakes" - "istio.io/istio/pilot/pkg/networking/plugin" - "istio.io/istio/pkg/config" -) - -func buildEnvoyFilterConfigStore(configPatches []*networking.EnvoyFilter_EnvoyConfigObjectPatch) *fakes.IstioConfigStore { - return &fakes.IstioConfigStore{ - ListStub: func(typ, namespace string) (configs []model.Config, e error) { - if typ == "envoy-filter" { - configs = []model.Config{ - { - ConfigMeta: model.ConfigMeta{ - Name: "test-envoyfilter", - Namespace: "not-default", - }, - Spec: &networking.EnvoyFilter{ - ConfigPatches: configPatches, - }, - }, - } - } - return - }, - EnvoyFilterStub: func(workloadLabels config.LabelsCollection) *model.Config { - return &model.Config{ - ConfigMeta: model.ConfigMeta{ - Name: "test-envoyfilter", - Namespace: "not-default", - }, - Spec: &networking.EnvoyFilter{ - ConfigPatches: configPatches, - }, - } - }, - } -} - -func buildPatchStruct(config string) *types.Struct { - val := &types.Struct{} - _ = jsonpb.Unmarshal(strings.NewReader(config), val) - return val -} - -func TestApplyClusterPatches(t *testing.T) { - configPatches := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ - { - ApplyTo: networking.EnvoyFilter_CLUSTER, - Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_SIDECAR_OUTBOUND, - }, - Patch: &networking.EnvoyFilter_Patch{ - Operation: networking.EnvoyFilter_Patch_ADD, - Value: buildPatchStruct(`{"name":"new-cluster1"}`), - }, - }, - { - ApplyTo: networking.EnvoyFilter_CLUSTER, - Patch: &networking.EnvoyFilter_Patch{ - Operation: networking.EnvoyFilter_Patch_ADD, - Value: buildPatchStruct(`{"name":"new-cluster2"}`), - }, - }, - { - ApplyTo: networking.EnvoyFilter_CLUSTER, - Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_GATEWAY, - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ - Cluster: &networking.EnvoyFilter_ClusterMatch{ - Service: "gateway.com", - }, - }, - }, - Patch: &networking.EnvoyFilter_Patch{Operation: networking.EnvoyFilter_Patch_REMOVE}, - }, - { - ApplyTo: networking.EnvoyFilter_CLUSTER, - Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_SIDECAR_INBOUND, - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ - Cluster: &networking.EnvoyFilter_ClusterMatch{ - PortNumber: 9999, - }, - }, - }, - Patch: &networking.EnvoyFilter_Patch{Operation: networking.EnvoyFilter_Patch_REMOVE}, - }, - { - ApplyTo: networking.EnvoyFilter_CLUSTER, - Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_ANY, - }, - Patch: &networking.EnvoyFilter_Patch{ - Operation: networking.EnvoyFilter_Patch_MERGE, - Value: buildPatchStruct(`{"dns_lookup_family":"V6_ONLY"}`), - }, - }, - { - ApplyTo: networking.EnvoyFilter_CLUSTER, - Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_ANY, - }, - Patch: &networking.EnvoyFilter_Patch{ - Operation: networking.EnvoyFilter_Patch_MERGE, - Value: buildPatchStruct(`{"lb_policy":"RING_HASH"}`), - }, - }, - } - - sidecarInput := []*xdsapi.Cluster{ - {Name: "cluster1", DnsLookupFamily: xdsapi.Cluster_V4_ONLY, LbPolicy: xdsapi.Cluster_ROUND_ROBIN}, - {Name: "cluster2", - Http2ProtocolOptions: &core.Http2ProtocolOptions{ - AllowConnect: true, - AllowMetadata: true, - }, LbPolicy: xdsapi.Cluster_MAGLEV, - }, - {Name: "inbound|9999||mgmtCluster"}, - } - sidecarOutput := []*xdsapi.Cluster{ - {Name: "cluster1", DnsLookupFamily: xdsapi.Cluster_V6_ONLY, LbPolicy: xdsapi.Cluster_RING_HASH}, - {Name: "cluster2", - Http2ProtocolOptions: &core.Http2ProtocolOptions{ - AllowConnect: true, - AllowMetadata: true, - }, LbPolicy: xdsapi.Cluster_RING_HASH, DnsLookupFamily: xdsapi.Cluster_V6_ONLY, - }, - {Name: "new-cluster1"}, - {Name: "new-cluster2"}, - } - - gatewayInput := []*xdsapi.Cluster{ - {Name: "cluster1", DnsLookupFamily: xdsapi.Cluster_V4_ONLY, LbPolicy: xdsapi.Cluster_ROUND_ROBIN}, - {Name: "cluster2", - Http2ProtocolOptions: &core.Http2ProtocolOptions{ - AllowConnect: true, - AllowMetadata: true, - }, LbPolicy: xdsapi.Cluster_MAGLEV, - }, - {Name: "inbound|9999||mgmtCluster"}, - {Name: "outbound|443||gateway.com"}, - } - gatewayOutput := []*xdsapi.Cluster{ - {Name: "cluster1", DnsLookupFamily: xdsapi.Cluster_V6_ONLY, LbPolicy: xdsapi.Cluster_RING_HASH}, - {Name: "cluster2", - Http2ProtocolOptions: &core.Http2ProtocolOptions{ - AllowConnect: true, - AllowMetadata: true, - }, LbPolicy: xdsapi.Cluster_RING_HASH, DnsLookupFamily: xdsapi.Cluster_V6_ONLY, - }, - {Name: "inbound|9999||mgmtCluster", DnsLookupFamily: xdsapi.Cluster_V6_ONLY, LbPolicy: xdsapi.Cluster_RING_HASH}, - {Name: "new-cluster2"}, - } - - testCases := []struct { - name string - input []*xdsapi.Cluster - proxy *model.Proxy - output []*xdsapi.Cluster - }{ - { - name: "sidecar cds patch", - input: sidecarInput, - proxy: &model.Proxy{Type: model.SidecarProxy, ConfigNamespace: "not-default"}, - output: sidecarOutput, - }, - { - name: "gateway cds patch", - input: gatewayInput, - proxy: &model.Proxy{Type: model.Router, ConfigNamespace: "not-default"}, - output: gatewayOutput, - }, - } - - serviceDiscovery := &fakes.ServiceDiscovery{} - env := newTestEnvironment(serviceDiscovery, testMesh, buildEnvoyFilterConfigStore(configPatches)) - push := model.NewPushContext() - push.InitContext(env) - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - ret := applyClusterPatches(env, tc.proxy, push, tc.input) - if !reflect.DeepEqual(tc.output, ret) { - t.Errorf("test case %s: expecting %v but got %v", tc.name, tc.output, ret) - } - }) - } -} - -func TestListenerMatch(t *testing.T) { - inputParams := &plugin.InputParams{ - ListenerProtocol: plugin.ListenerProtocolHTTP, - Node: &model.Proxy{ - Type: model.SidecarProxy, - }, - Port: &model.Port{ - Name: "http-foo", - Port: 80, - }, - } - - testCases := []struct { - name string - inputParams *plugin.InputParams - listenerIP net.IP - matchCondition *networking.EnvoyFilter_DeprecatedListenerMatch - direction networking.EnvoyFilter_DeprecatedListenerMatch_ListenerType - result bool - }{ - { - name: "empty match", - inputParams: inputParams, - result: true, - }, - { - name: "match by port", - inputParams: inputParams, - matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{PortNumber: 80}, - result: true, - }, - { - name: "match by port name prefix", - inputParams: inputParams, - matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{PortNamePrefix: "http"}, - result: true, - }, - { - name: "match by listener type", - inputParams: inputParams, - direction: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, - matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ListenerType: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND}, - result: true, - }, - { - name: "match by listener protocol", - inputParams: inputParams, - matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ListenerProtocol: networking.EnvoyFilter_DeprecatedListenerMatch_HTTP}, - result: true, - }, - { - name: "match by listener address with CIDR", - inputParams: inputParams, - listenerIP: net.ParseIP("10.10.10.10"), - matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{Address: []string{"10.10.10.10/24", "192.168.0.1/24"}}, - result: true, - }, - { - name: "match outbound sidecar http listeners on 10.10.10.0/24:80, with port name prefix http-*", - inputParams: inputParams, - listenerIP: net.ParseIP("10.10.10.10"), - direction: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, - matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ - PortNumber: 80, - PortNamePrefix: "http", - ListenerType: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, - ListenerProtocol: networking.EnvoyFilter_DeprecatedListenerMatch_HTTP, - Address: []string{"10.10.10.0/24"}, - }, - result: true, - }, - { - name: "does not match: outbound sidecar http listeners on 10.10.10.0/24:80, with port name prefix tcp-*", - inputParams: inputParams, - listenerIP: net.ParseIP("10.10.10.10"), - direction: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, - matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ - PortNumber: 80, - PortNamePrefix: "tcp", - ListenerType: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, - ListenerProtocol: networking.EnvoyFilter_DeprecatedListenerMatch_HTTP, - Address: []string{"10.10.10.0/24"}, - }, - result: false, - }, - { - name: "does not match: inbound sidecar http listeners with port name prefix http-*", - inputParams: inputParams, - direction: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, - matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ - PortNamePrefix: "http", - ListenerType: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_INBOUND, - ListenerProtocol: networking.EnvoyFilter_DeprecatedListenerMatch_HTTP, - }, - result: false, - }, - { - name: "does not match: outbound gateway http listeners on 10.10.10.0/24:80, with port name prefix http-*", - inputParams: inputParams, - listenerIP: net.ParseIP("10.10.10.10"), - direction: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, - matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ - PortNumber: 80, - PortNamePrefix: "http", - ListenerType: networking.EnvoyFilter_DeprecatedListenerMatch_GATEWAY, - ListenerProtocol: networking.EnvoyFilter_DeprecatedListenerMatch_HTTP, - Address: []string{"10.10.10.0/24"}, - }, - result: false, - }, - { - name: "does not match: outbound sidecar listeners on 172.16.0.1/16:80, with port name prefix http-*", - inputParams: inputParams, - listenerIP: net.ParseIP("10.10.10.10"), - direction: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, - matchCondition: &networking.EnvoyFilter_DeprecatedListenerMatch{ - PortNumber: 80, - PortNamePrefix: "http", - ListenerType: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, - ListenerProtocol: networking.EnvoyFilter_DeprecatedListenerMatch_HTTP, - Address: []string{"172.16.0.1/16"}, - }, - result: false, - }, - } - - for _, tc := range testCases { - tc.inputParams.DeprecatedListenerCategory = tc.direction - ret := deprecatedListenerMatch(tc.inputParams, tc.listenerIP, tc.matchCondition) - if tc.result != ret { - t.Errorf("%s: expecting %v but got %v", tc.name, tc.result, ret) - } - } -} - -func Test_virtualHostMatch(t *testing.T) { - type args struct { - match *networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch - vh *route.VirtualHost - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "match & vh is nil", - args: args{}, - want: true, - }, - { - name: "match is nil", - args: args{ - vh: &route.VirtualHost{ - Name: "scooby", - }, - }, - want: true, - }, - { - name: "vh is nil", - args: args{ - match: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{}, - }, - want: false, - }, - { - name: "full match", - args: args{ - vh: &route.VirtualHost{ - Name: "scooby", - }, - match: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ - Name: "scooby", - }, - }, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := virtualHostMatch(tt.args.match, tt.args.vh); got != tt.want { - t.Errorf("virtualHostMatch() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_routeConfigurationMatch(t *testing.T) { - type args struct { - rc *xdsapi.RouteConfiguration - vh *route.VirtualHost - pluginParams *plugin.InputParams - matchCondition *networking.EnvoyFilter_EnvoyConfigObjectMatch - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "nil match", - args: args{}, - want: true, - }, - { - name: "context mismatch", - args: args{ - pluginParams: &plugin.InputParams{ - ListenerCategory: networking.EnvoyFilter_SIDECAR_OUTBOUND, - }, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_GATEWAY, - }, - }, - want: false, - }, - { - name: "nil route match", - args: args{ - pluginParams: &plugin.InputParams{ - ListenerCategory: networking.EnvoyFilter_SIDECAR_OUTBOUND, - }, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_SIDECAR_OUTBOUND, - }, - }, - want: true, - }, - { - name: "rc name mismatch", - args: args{ - pluginParams: &plugin.InputParams{ - ListenerCategory: networking.EnvoyFilter_SIDECAR_OUTBOUND, - Node: &model.Proxy{Type: model.SidecarProxy}, - }, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_SIDECAR_OUTBOUND, - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ - RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{Name: "scooby.80"}, - }, - }, - rc: &xdsapi.RouteConfiguration{Name: "scooby.90"}, - }, - want: false, - }, - { - name: "vh name mismatch", - args: args{ - pluginParams: &plugin.InputParams{ - ListenerCategory: networking.EnvoyFilter_SIDECAR_OUTBOUND, - Node: &model.Proxy{Type: model.SidecarProxy}, - }, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_SIDECAR_OUTBOUND, - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ - RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ - Name: "scooby.80", - Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ - Name: "scoobydoo", - }, - }, - }, - }, - rc: &xdsapi.RouteConfiguration{Name: "scooby.80"}, - vh: &route.VirtualHost{Name: "scrappy"}, - }, - want: false, - }, - { - name: "sidecar port match, vh name match", - args: args{ - pluginParams: &plugin.InputParams{ - ListenerCategory: networking.EnvoyFilter_SIDECAR_OUTBOUND, - Port: &model.Port{Port: 80}, - Node: &model.Proxy{Type: model.SidecarProxy}, - }, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_SIDECAR_OUTBOUND, - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ - RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ - PortNumber: 80, - Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ - Name: "scoobydoo", - }, - }, - }, - }, - rc: &xdsapi.RouteConfiguration{Name: "80"}, - vh: &route.VirtualHost{Name: "scoobydoo"}, - }, - want: true, - }, - { - name: "sidecar port mismatch", - args: args{ - pluginParams: &plugin.InputParams{ - ListenerCategory: networking.EnvoyFilter_SIDECAR_OUTBOUND, - Port: &model.Port{Port: 80}, - Node: &model.Proxy{Type: model.SidecarProxy}, - }, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_ANY, - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ - RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ - PortNumber: 90, - Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ - Name: "scoobydoo", - }, - }, - }, - }, - rc: &xdsapi.RouteConfiguration{Name: "80"}, - vh: &route.VirtualHost{Name: "scoobydoo"}, - }, - want: false, - }, - { - name: "gateway fields match", - args: args{ - pluginParams: &plugin.InputParams{ - ListenerCategory: networking.EnvoyFilter_GATEWAY, - Node: &model.Proxy{Type: model.Router}, - }, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_GATEWAY, - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ - RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ - PortNumber: 443, - PortName: "app1", - Gateway: "ns1/gw1", - Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ - Name: "scoobydoo", - }, - }, - }, - }, - rc: &xdsapi.RouteConfiguration{Name: "https.443.app1.gw1.ns1"}, - vh: &route.VirtualHost{Name: "scoobydoo"}, - }, - want: true, - }, - { - name: "gateway fields mismatch", - args: args{ - pluginParams: &plugin.InputParams{ - ListenerCategory: networking.EnvoyFilter_GATEWAY, - Node: &model.Proxy{Type: model.Router}, - }, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_GATEWAY, - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ - RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ - PortNumber: 443, - PortName: "app1", - Gateway: "ns1/gw1", - Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ - Name: "scoobydoo", - }, - }, - }, - }, - rc: &xdsapi.RouteConfiguration{Name: "http.80"}, - vh: &route.VirtualHost{Name: "scoobydoo"}, - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := routeConfigurationMatch(tt.args.rc, tt.args.vh, tt.args.pluginParams, tt.args.matchCondition); got != tt.want { - t.Errorf("routeConfigurationMatch() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_clusterMatch(t *testing.T) { - type args struct { - proxy *model.Proxy - cluster *xdsapi.Cluster - matchCondition *networking.EnvoyFilter_EnvoyConfigObjectMatch - operation networking.EnvoyFilter_Patch_Operation - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "nil match", - args: args{}, - want: true, - }, - { - name: "add op match with gateway", - args: args{ - proxy: &model.Proxy{Type: model.Router}, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{Context: networking.EnvoyFilter_GATEWAY}, - operation: networking.EnvoyFilter_Patch_ADD, - }, - want: true, - }, - { - name: "add op match with sidecar", - args: args{ - proxy: &model.Proxy{Type: model.SidecarProxy}, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{Context: networking.EnvoyFilter_SIDECAR_INBOUND}, - operation: networking.EnvoyFilter_Patch_ADD, - }, - want: true, - }, - { - name: "add op mismatch with gateway", - args: args{ - proxy: &model.Proxy{Type: model.Router}, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{Context: networking.EnvoyFilter_SIDECAR_OUTBOUND}, - operation: networking.EnvoyFilter_Patch_ADD, - }, - want: false, - }, - { - name: "merge op mismatch with gateway", - args: args{ - proxy: &model.Proxy{Type: model.Router}, - cluster: &xdsapi.Cluster{Name: "somecluster"}, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{Context: networking.EnvoyFilter_SIDECAR_OUTBOUND}, - operation: networking.EnvoyFilter_Patch_MERGE, - }, - want: false, - }, - { - name: "merge op match with gateway", - args: args{ - proxy: &model.Proxy{Type: model.Router}, - cluster: &xdsapi.Cluster{Name: "somecluster"}, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{Context: networking.EnvoyFilter_GATEWAY}, - operation: networking.EnvoyFilter_Patch_MERGE, - }, - want: true, - }, - { - name: "merge op match with sidecar inbound", - args: args{ - proxy: &model.Proxy{Type: model.SidecarProxy}, - cluster: &xdsapi.Cluster{Name: "inbound|80|v1|foo.com"}, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{Context: networking.EnvoyFilter_SIDECAR_INBOUND}, - operation: networking.EnvoyFilter_Patch_MERGE, - }, - want: true, - }, - { - name: "merge op mismatch with sidecar outbound", - args: args{ - proxy: &model.Proxy{Type: model.SidecarProxy}, - cluster: &xdsapi.Cluster{Name: "outbound|80|v1|foo.com"}, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{Context: networking.EnvoyFilter_SIDECAR_INBOUND}, - operation: networking.EnvoyFilter_Patch_MERGE, - }, - want: false, - }, - { - name: "name mismatch", - args: args{ - proxy: &model.Proxy{Type: model.SidecarProxy}, - operation: networking.EnvoyFilter_Patch_MERGE, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ - Cluster: &networking.EnvoyFilter_ClusterMatch{Name: "scooby"}, - }, - }, - cluster: &xdsapi.Cluster{Name: "scrappy"}, - }, - want: false, - }, - { - name: "subset mismatch", - args: args{ - proxy: &model.Proxy{Type: model.SidecarProxy}, - operation: networking.EnvoyFilter_Patch_MERGE, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ - Cluster: &networking.EnvoyFilter_ClusterMatch{ - PortNumber: 80, - Service: "foo.bar", - Subset: "v1", - }, - }, - }, - cluster: &xdsapi.Cluster{Name: "outbound|80|v2|foo.bar"}, - }, - want: false, - }, - { - name: "service mismatch", - args: args{ - proxy: &model.Proxy{Type: model.SidecarProxy}, - operation: networking.EnvoyFilter_Patch_MERGE, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ - Cluster: &networking.EnvoyFilter_ClusterMatch{ - PortNumber: 80, - Service: "foo.bar", - Subset: "v1", - }, - }, - }, - cluster: &xdsapi.Cluster{Name: "outbound|80|v1|google.com"}, - }, - want: false, - }, - { - name: "port mismatch", - args: args{ - proxy: &model.Proxy{Type: model.SidecarProxy}, - operation: networking.EnvoyFilter_Patch_MERGE, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ - Cluster: &networking.EnvoyFilter_ClusterMatch{ - PortNumber: 80, - Service: "foo.bar", - Subset: "v1", - }, - }, - }, - cluster: &xdsapi.Cluster{Name: "outbound|90|v1|foo.bar"}, - }, - want: false, - }, - { - name: "full match", - args: args{ - proxy: &model.Proxy{Type: model.SidecarProxy}, - operation: networking.EnvoyFilter_Patch_MERGE, - matchCondition: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ - Cluster: &networking.EnvoyFilter_ClusterMatch{ - PortNumber: 80, - Service: "foo.bar", - Subset: "v1", - }, - }, - }, - cluster: &xdsapi.Cluster{Name: "outbound|80|v1|foo.bar"}, - }, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := clusterMatch(tt.args.proxy, tt.args.cluster, tt.args.matchCondition, tt.args.operation); got != tt.want { - t.Errorf("clusterMatch() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_applyRouteConfigurationPatches(t *testing.T) { - configPatches := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ - { - ApplyTo: networking.EnvoyFilter_ROUTE_CONFIGURATION, - Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_SIDECAR_OUTBOUND, - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ - RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ - PortNumber: 80, - }, - }, - }, - Patch: &networking.EnvoyFilter_Patch{ - Operation: networking.EnvoyFilter_Patch_MERGE, - Value: buildPatchStruct(`{"request_headers_to_remove":["h3", "h4"]}`), - }, - }, - { - ApplyTo: networking.EnvoyFilter_VIRTUAL_HOST, - Patch: &networking.EnvoyFilter_Patch{ - Operation: networking.EnvoyFilter_Patch_ADD, - Value: buildPatchStruct(`{"name":"new-vhost"}`), - }, - }, - { - ApplyTo: networking.EnvoyFilter_VIRTUAL_HOST, - Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_GATEWAY, - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ - RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ - Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ - Name: "vhost1", - }, - }, - }, - }, - Patch: &networking.EnvoyFilter_Patch{Operation: networking.EnvoyFilter_Patch_REMOVE}, - }, - { - ApplyTo: networking.EnvoyFilter_VIRTUAL_HOST, - Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_SIDECAR_INBOUND, - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{ - RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{ - Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{ - Name: "vhost2", - }, - }, - }, - }, - Patch: &networking.EnvoyFilter_Patch{Operation: networking.EnvoyFilter_Patch_REMOVE}, - }, - { - ApplyTo: networking.EnvoyFilter_VIRTUAL_HOST, - Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_ANY, - }, - Patch: &networking.EnvoyFilter_Patch{ - Operation: networking.EnvoyFilter_Patch_MERGE, - Value: buildPatchStruct(`{"domains":["domain:80"]}`), - }, - }, - } - - sidecarOutboundRC := &xdsapi.RouteConfiguration{ - Name: "80", - VirtualHosts: []route.VirtualHost{ - { - Name: "foo.com", - Domains: []string{"domain"}, - }, - }, - RequestHeadersToRemove: []string{"h1", "h2"}, - } - patchedSidecarOutputRC := &xdsapi.RouteConfiguration{ - Name: "80", - VirtualHosts: []route.VirtualHost{ - { - Name: "foo.com", - Domains: []string{"domain", "domain:80"}, - }, - { - Name: "new-vhost", - }, - }, - RequestHeadersToRemove: []string{"h1", "h2", "h3", "h4"}, - } - sidecarInboundRC := &xdsapi.RouteConfiguration{ - Name: "inbound|http|80", - VirtualHosts: []route.VirtualHost{ - { - Name: "vhost2", - Domains: []string{"domain"}, - }, - }, - } - patchedSidecarInboundRC := &xdsapi.RouteConfiguration{ - Name: "inbound|http|80", - VirtualHosts: []route.VirtualHost{ - { - Name: "new-vhost", - }, - }, - } - - gatewayRC := &xdsapi.RouteConfiguration{ - Name: "80", - VirtualHosts: []route.VirtualHost{ - { - Name: "vhost1", - Domains: []string{"domain"}, - }, - { - Name: "gateway", - Domains: []string{"gateway"}, - }, - }, - } - patchedGatewayRC := &xdsapi.RouteConfiguration{ - Name: "80", - VirtualHosts: []route.VirtualHost{ - { - Name: "gateway", - Domains: []string{"gateway", "domain:80"}, - }, - { - Name: "new-vhost", - }, - }, - } - - serviceDiscovery := &fakes.ServiceDiscovery{} - env := newTestEnvironment(serviceDiscovery, testMesh, buildEnvoyFilterConfigStore(configPatches)) - push := model.NewPushContext() - push.InitContext(env) - - sidecarOutbundPluginParams := &plugin.InputParams{ - ListenerCategory: networking.EnvoyFilter_SIDECAR_OUTBOUND, - Env: env, - Node: &model.Proxy{Type: model.SidecarProxy, ConfigNamespace: "not-default"}, - Port: &model.Port{Port: 80}, - Push: push, - } - sidecarInboundPluginParams := &plugin.InputParams{ - ListenerCategory: networking.EnvoyFilter_SIDECAR_INBOUND, - Env: env, - Node: &model.Proxy{Type: model.SidecarProxy, ConfigNamespace: "not-default"}, - Port: &model.Port{Port: 80}, - Push: push, - } - gatewayPluginParams := &plugin.InputParams{ - ListenerCategory: networking.EnvoyFilter_GATEWAY, - Env: env, - Node: &model.Proxy{Type: model.Router, ConfigNamespace: "not-default"}, - Push: push, - } - - type args struct { - pluginParams *plugin.InputParams - routeConfiguration *xdsapi.RouteConfiguration - } - tests := []struct { - name string - args args - want *xdsapi.RouteConfiguration - }{ - { - name: "sidecar outbound rds patch", - args: args{ - pluginParams: sidecarOutbundPluginParams, - routeConfiguration: sidecarOutboundRC, - }, - want: patchedSidecarOutputRC, - }, - { - name: "sidecar inbound rc patch", - args: args{ - pluginParams: sidecarInboundPluginParams, - routeConfiguration: sidecarInboundRC, - }, - want: patchedSidecarInboundRC, - }, - { - name: "gateway rds patch", - args: args{ - pluginParams: gatewayPluginParams, - routeConfiguration: gatewayRC, - }, - want: patchedGatewayRC, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := applyRouteConfigurationPatches(tt.args.pluginParams, tt.args.routeConfiguration); !reflect.DeepEqual(got, tt.want) { - t.Errorf("applyRouteConfigurationPatches() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pilot/pkg/networking/core/v1alpha3/gateway.go b/pilot/pkg/networking/core/v1alpha3/gateway.go index e9a398b5eb5c..9959873f5871 100644 --- a/pilot/pkg/networking/core/v1alpha3/gateway.go +++ b/pilot/pkg/networking/core/v1alpha3/gateway.go @@ -196,7 +196,7 @@ func (configgen *ConfigGeneratorImpl) buildGatewayListeners(env *model.Environme } func (configgen *ConfigGeneratorImpl) buildGatewayHTTPRouteConfig(env *model.Environment, node *model.Proxy, push *model.PushContext, - proxyInstances []*model.ServiceInstance, routeName string) (*xdsapi.RouteConfiguration, error) { + proxyInstances []*model.ServiceInstance, routeName string) *xdsapi.RouteConfiguration { services := push.Services(node) @@ -209,7 +209,7 @@ func (configgen *ConfigGeneratorImpl) buildGatewayHTTPRouteConfig(env *model.Env gateways := env.Gateways(workloadLabels) if len(gateways) == 0 { log.Debuga("buildGatewayRoutes: no gateways for router ", node.ID) - return nil, nil + return nil } merged := model.MergeGateways(gateways...) @@ -221,7 +221,7 @@ func (configgen *ConfigGeneratorImpl) buildGatewayHTTPRouteConfig(env *model.Env // This can happen when a gateway has recently been deleted. Envoy will still request route // information due to the draining of listeners, so we should not return an error. - return nil, nil + return nil } servers := merged.ServersByRouteName[routeName] @@ -320,8 +320,7 @@ func (configgen *ConfigGeneratorImpl) buildGatewayHTTPRouteConfig(env *model.Env p.OnOutboundRouteConfiguration(in, routeCfg) } - routeCfg = applyRouteConfigurationPatches(in, routeCfg) - return routeCfg, nil + return routeCfg } // builds a HTTP connection manager for servers of type HTTP or HTTPS (mode: simple/mutual) diff --git a/pilot/pkg/networking/core/v1alpha3/gateway_test.go b/pilot/pkg/networking/core/v1alpha3/gateway_test.go index 1f0fd6dc244b..0272d8869229 100644 --- a/pilot/pkg/networking/core/v1alpha3/gateway_test.go +++ b/pilot/pkg/networking/core/v1alpha3/gateway_test.go @@ -489,10 +489,8 @@ func TestGatewayHTTPRouteConfig(t *testing.T) { p := &fakePlugin{} configgen := NewConfigGenerator([]plugin.Plugin{p}) env := buildEnv(t, tt.gateways, tt.virtualServices) - route, err := configgen.buildGatewayHTTPRouteConfig(&env, &proxy, env.PushContext, proxyInstances, tt.routeName) - if err != nil { - t.Error(err) - } + + route := configgen.buildGatewayHTTPRouteConfig(&env, &proxy, env.PushContext, proxyInstances, tt.routeName) vh := make([]string, 0) for _, h := range route.VirtualHosts { vh = append(vh, h.Name) diff --git a/pilot/pkg/networking/core/v1alpha3/httproute.go b/pilot/pkg/networking/core/v1alpha3/httproute.go index 17a3b5978d2a..26373c2f08bc 100644 --- a/pilot/pkg/networking/core/v1alpha3/httproute.go +++ b/pilot/pkg/networking/core/v1alpha3/httproute.go @@ -25,6 +25,7 @@ import ( networking "istio.io/api/networking/v1alpha3" "istio.io/istio/pilot/pkg/features" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pilot/pkg/networking/core/v1alpha3/envoyfilter" istio_route "istio.io/istio/pilot/pkg/networking/core/v1alpha3/route" "istio.io/istio/pilot/pkg/networking/plugin" "istio.io/istio/pilot/pkg/networking/util" @@ -38,11 +39,21 @@ func (configgen *ConfigGeneratorImpl) BuildHTTPRoutes(env *model.Environment, no routeName string) (*xdsapi.RouteConfiguration, error) { // TODO: Move all this out proxyInstances := node.ServiceInstances + var rc *xdsapi.RouteConfiguration + var err error switch node.Type { case model.SidecarProxy: - return configgen.buildSidecarOutboundHTTPRouteConfig(env, node, push, proxyInstances, routeName), nil + rc = configgen.buildSidecarOutboundHTTPRouteConfig(env, node, push, proxyInstances, routeName) + if rc != nil { + rc = envoyfilter.ApplyRouteConfigurationPatches(networking.EnvoyFilter_SIDECAR_OUTBOUND, node, push, rc) + } + return rc, nil case model.Router: - return configgen.buildGatewayHTTPRouteConfig(env, node, push, proxyInstances, routeName) + rc = configgen.buildGatewayHTTPRouteConfig(env, node, push, proxyInstances, routeName) + if rc != nil { + rc = envoyfilter.ApplyRouteConfigurationPatches(networking.EnvoyFilter_GATEWAY, node, push, rc) + } + return rc, err } return nil, nil } @@ -89,7 +100,7 @@ func (configgen *ConfigGeneratorImpl) buildSidecarInboundHTTPRouteConfig(env *mo p.OnInboundRouteConfiguration(in, r) } - r = applyRouteConfigurationPatches(in, r) + r = envoyfilter.ApplyRouteConfigurationPatches(networking.EnvoyFilter_SIDECAR_INBOUND, in.Node, in.Push, r) return r } @@ -273,7 +284,7 @@ func (configgen *ConfigGeneratorImpl) buildSidecarOutboundHTTPRouteConfig(env *m p.OnOutboundRouteConfiguration(pluginParams, out) } - out = applyRouteConfigurationPatches(pluginParams, out) + out = envoyfilter.ApplyRouteConfigurationPatches(pluginParams.ListenerCategory, pluginParams.Node, pluginParams.Push, out) return out } diff --git a/pilot/pkg/networking/core/v1alpha3/listener.go b/pilot/pkg/networking/core/v1alpha3/listener.go index 2c64bcb642a2..462b5005758b 100644 --- a/pilot/pkg/networking/core/v1alpha3/listener.go +++ b/pilot/pkg/networking/core/v1alpha3/listener.go @@ -40,6 +40,7 @@ import ( "istio.io/istio/pilot/pkg/features" "istio.io/istio/pilot/pkg/model" "istio.io/istio/pilot/pkg/monitoring" + "istio.io/istio/pilot/pkg/networking/core/v1alpha3/envoyfilter" "istio.io/istio/pilot/pkg/networking/plugin" "istio.io/istio/pilot/pkg/networking/util" authn_model "istio.io/istio/pilot/pkg/security/model" @@ -198,8 +199,7 @@ func (configgen *ConfigGeneratorImpl) BuildListeners(env *model.Environment, nod builder = configgen.buildGatewayListeners(env, node, push) } - builder = applyListenerPatches(node, push, builder) - + builder.patchListeners(push) return builder.getListeners(), err } @@ -209,10 +209,8 @@ func (configgen *ConfigGeneratorImpl) buildSidecarListeners(env *model.Environme mesh := env.Mesh proxyInstances := node.ServiceInstances - noneMode := node.GetInterceptionMode() == model.InterceptionNone - _, actualLocalHostAddress := getActualWildcardAndLocalHost(node) - builder := NewListenerBuilder(node) + if mesh.ProxyListenPort > 0 { // Any build order change need a careful code review builder = NewListenerBuilder(node). @@ -223,62 +221,6 @@ func (configgen *ConfigGeneratorImpl) buildSidecarListeners(env *model.Environme buildVirtualInboundListener(env, node) } - httpProxyPort := mesh.ProxyHttpPort - if httpProxyPort == 0 && noneMode { // make sure http proxy is enabled for 'none' interception. - httpProxyPort = int32(features.DefaultPortHTTPProxy) - } - // enable HTTP PROXY port if necessary; this will add an RDS route for this port - if httpProxyPort > 0 { - traceOperation := http_conn.EGRESS - listenAddress := actualLocalHostAddress - - httpOpts := &core.Http1ProtocolOptions{ - AllowAbsoluteUrl: proto.BoolTrue, - } - if features.HTTP10 || node.Metadata[model.NodeMetadataHTTP10] == "1" { - httpOpts.AcceptHttp_10 = true - } - - opts := buildListenerOpts{ - env: env, - proxy: node, - proxyInstances: proxyInstances, - bind: listenAddress, - port: int(httpProxyPort), - filterChainOpts: []*filterChainOpts{{ - httpOpts: &httpListenerOpts{ - rds: RDSHttpProxy, - useRemoteAddress: false, - direction: traceOperation, - connectionManager: &http_conn.HttpConnectionManager{ - HttpProtocolOptions: httpOpts, - }, - }, - }}, - bindToPort: true, - skipUserFilters: true, - } - l := buildListener(opts) - // TODO: plugins for HTTP_PROXY mode, there is no mixer for http_proxy - mutable := &plugin.MutableObjects{ - Listener: l, - FilterChains: []plugin.FilterChain{{}}, - } - pluginParams := &plugin.InputParams{ - ListenerProtocol: plugin.ListenerProtocolHTTP, - DeprecatedListenerCategory: networking.EnvoyFilter_DeprecatedListenerMatch_SIDECAR_OUTBOUND, - Env: env, - Node: node, - ProxyInstances: proxyInstances, - Push: push, - } - if err := buildCompleteFilterChain(pluginParams, mutable, opts); err != nil { - log.Warna("buildSidecarListeners ", err.Error()) - } else { - builder.outboundListeners = append(builder.outboundListeners, l) - } - } - return builder } @@ -834,7 +776,78 @@ func (configgen *ConfigGeneratorImpl) buildSidecarOutboundListeners(env *model.E } } - return append(tcpListeners, httpListeners...) + tcpListeners = append(tcpListeners, httpListeners...) + httpProxy := configgen.buildHTTPProxy(env, node, push, proxyInstances) + if httpProxy != nil { + tcpListeners = append(tcpListeners, httpProxy) + } + + return tcpListeners +} + +func (configgen *ConfigGeneratorImpl) buildHTTPProxy(env *model.Environment, node *model.Proxy, + push *model.PushContext, proxyInstances []*model.ServiceInstance) *xdsapi.Listener { + httpProxyPort := env.Mesh.ProxyHttpPort + noneMode := node.GetInterceptionMode() == model.InterceptionNone + _, actualLocalHostAddress := getActualWildcardAndLocalHost(node) + + if httpProxyPort == 0 && noneMode { // make sure http proxy is enabled for 'none' interception. + httpProxyPort = int32(features.DefaultPortHTTPProxy) + } + // enable HTTP PROXY port if necessary; this will add an RDS route for this port + if httpProxyPort == 0 { + return nil + } + + traceOperation := http_conn.EGRESS + listenAddress := actualLocalHostAddress + + httpOpts := &core.Http1ProtocolOptions{ + AllowAbsoluteUrl: proto.BoolTrue, + } + if features.HTTP10 || node.Metadata[model.NodeMetadataHTTP10] == "1" { + httpOpts.AcceptHttp_10 = true + } + + opts := buildListenerOpts{ + env: env, + proxy: node, + proxyInstances: proxyInstances, + bind: listenAddress, + port: int(httpProxyPort), + filterChainOpts: []*filterChainOpts{{ + httpOpts: &httpListenerOpts{ + rds: RDSHttpProxy, + useRemoteAddress: false, + direction: traceOperation, + connectionManager: &http_conn.HttpConnectionManager{ + HttpProtocolOptions: httpOpts, + }, + }, + }}, + bindToPort: true, + skipUserFilters: true, + } + l := buildListener(opts) + // TODO: plugins for HTTP_PROXY mode, envoyfilter needs another listener match for SIDECAR_HTTP_PROXY + // there is no mixer for http_proxy + mutable := &plugin.MutableObjects{ + Listener: l, + FilterChains: []plugin.FilterChain{{}}, + } + pluginParams := &plugin.InputParams{ + ListenerProtocol: plugin.ListenerProtocolHTTP, + ListenerCategory: networking.EnvoyFilter_SIDECAR_OUTBOUND, + Env: env, + Node: node, + ProxyInstances: proxyInstances, + Push: push, + } + if err := buildCompleteFilterChain(pluginParams, mutable, opts); err != nil { + log.Warna("buildSidecarListeners ", err.Error()) + return nil + } + return l } // validatePort checks if the sidecar proxy is capable of listening on a @@ -1321,7 +1334,7 @@ func (configgen *ConfigGeneratorImpl) onVirtualOutboundListener(env *model.Envir return ipTablesListener } -// Deprecated: buildSidecarInboundMgmtListeners creates inbound TCP only listeners for the management ports on +// buildSidecarInboundMgmtListeners creates inbound TCP only listeners for the management ports on // server (inbound). Management port listeners are slightly different from standard Inbound listeners // in that, they do not have mixer filters nor do they have inbound auth. // N.B. If a given management port is same as the service instance's endpoint port @@ -1771,7 +1784,7 @@ func buildCompleteFilterChain(pluginParams *plugin.InputParams, mutable *plugin. // EnvoyFilter crd could choose to replace the HTTP ConnectionManager that we built or can choose to add // more filters to the HTTP filter chain. In the latter case, the deprecatedInsertUserFilters function will // overwrite the HTTP connection manager in the filter chain after inserting the new filters - return deprecatedInsertUserFilters(pluginParams, mutable.Listener, httpConnectionManagers) + return envoyfilter.DeprecatedInsertUserFilters(pluginParams, mutable.Listener, httpConnectionManagers) } return nil diff --git a/pilot/pkg/networking/core/v1alpha3/listener_builder.go b/pilot/pkg/networking/core/v1alpha3/listener_builder.go index e4b9bf39581d..43cfba7199d3 100644 --- a/pilot/pkg/networking/core/v1alpha3/listener_builder.go +++ b/pilot/pkg/networking/core/v1alpha3/listener_builder.go @@ -22,10 +22,10 @@ import ( xdsutil "github.com/envoyproxy/go-control-plane/pkg/util" google_protobuf "github.com/gogo/protobuf/types" - "istio.io/istio/pilot/pkg/features" - networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/features" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pilot/pkg/networking/core/v1alpha3/envoyfilter" "istio.io/istio/pilot/pkg/networking/util" "istio.io/istio/pkg/proto" "istio.io/pkg/log" @@ -203,6 +203,34 @@ func (builder *ListenerBuilder) buildVirtualInboundListener(env *model.Environme return builder } +func (builder *ListenerBuilder) patchListeners(push *model.PushContext) { + if builder.node.Type == model.Router { + envoyfilter.ApplyListenerPatches(networking.EnvoyFilter_GATEWAY, builder.node, push, builder.gatewayListeners, false) + return + } + + patchOneListener := func(listener *xdsapi.Listener) *xdsapi.Listener { + if listener == nil { + return nil + } + tempArray := []*xdsapi.Listener{listener} + tempArray = envoyfilter.ApplyListenerPatches(networking.EnvoyFilter_SIDECAR_OUTBOUND, builder.node, push, tempArray, true) + // temp array will either be empty [if virtual listener was removed] or will have a modified listener + if len(tempArray) == 0 { + return nil + } + return tempArray[0] + } + builder.virtualListener = patchOneListener(builder.virtualListener) + builder.virtualInboundListener = patchOneListener(builder.virtualInboundListener) + builder.managementListeners = envoyfilter.ApplyListenerPatches(networking.EnvoyFilter_SIDECAR_INBOUND, builder.node, + push, builder.managementListeners, true) + builder.inboundListeners = envoyfilter.ApplyListenerPatches(networking.EnvoyFilter_SIDECAR_INBOUND, builder.node, + push, builder.inboundListeners, false) + builder.outboundListeners = envoyfilter.ApplyListenerPatches(networking.EnvoyFilter_SIDECAR_INBOUND, builder.node, + push, builder.outboundListeners, false) +} + func (builder *ListenerBuilder) getListeners() []*xdsapi.Listener { if builder.node.Type == model.SidecarProxy { nInbound, nOutbound, nManagement := len(builder.inboundListeners), len(builder.outboundListeners), len(builder.managementListeners) diff --git a/tests/e2e/tests/pilot/testdata/networking/v1alpha3/envoyfilter-c.yaml b/tests/e2e/tests/pilot/testdata/networking/v1alpha3/envoyfilter-c.yaml index 910dbffb1b99..4ff1e80da956 100644 --- a/tests/e2e/tests/pilot/testdata/networking/v1alpha3/envoyfilter-c.yaml +++ b/tests/e2e/tests/pilot/testdata/networking/v1alpha3/envoyfilter-c.yaml @@ -3,22 +3,27 @@ kind: EnvoyFilter metadata: name: simple-envoy-filter spec: - workloadLabels: - app: c - filters: - - listenerMatch: - listenerType: SIDECAR_INBOUND - listenerProtocol: HTTP - insertPosition: - index: FIRST - filterType: HTTP - filterName: envoy.fault - filterConfig: - abort: - percentage: - numerator: 100 - denominator: HUNDRED - httpStatus: 444 - headers: - name: envoyfilter-test - exactMatch: foobar123 + workloadSelector: + labels: + app: c + configPatches: + - applyTo: HTTP_FILTER + match: + context: SIDECAR_INBOUND + listener: + filterChain: + filter: + name: "envoy.http_connection_manager" + patch: + operation: INSERT_BEFORE + value: + name: envoy.fault + config: + abort: + percentage: + numerator: 100 + denominator: HUNDRED + httpStatus: 444 + headers: + name: envoyfilter-test + exactMatch: foobar123 diff --git a/vendor/github.com/gogo/protobuf/jsonpb/jsonpb.go b/vendor/github.com/gogo/protobuf/jsonpb/jsonpb.go index 4c39d0a5f489..3893c02d4624 100644 --- a/vendor/github.com/gogo/protobuf/jsonpb/jsonpb.go +++ b/vendor/github.com/gogo/protobuf/jsonpb/jsonpb.go @@ -181,7 +181,12 @@ func (m *Marshaler) marshalObject(out *errWriter, v proto.Message, indent, typeU return fmt.Errorf("failed to marshal type URL %q to JSON: %v", typeURL, err) } js["@type"] = (*json.RawMessage)(&turl) - if b, err = json.Marshal(js); err != nil { + if m.Indent != "" { + b, err = json.MarshalIndent(js, indent, m.Indent) + } else { + b, err = json.Marshal(js) + } + if err != nil { return err } } diff --git a/vendor/github.com/gogo/protobuf/proto/extensions.go b/vendor/github.com/gogo/protobuf/proto/extensions.go index 686bd2a09d04..341c6f57f521 100644 --- a/vendor/github.com/gogo/protobuf/proto/extensions.go +++ b/vendor/github.com/gogo/protobuf/proto/extensions.go @@ -527,6 +527,7 @@ func ExtensionDescs(pb Message) ([]*ExtensionDesc, error) { // SetExtension sets the specified extension of pb to the specified value. func SetExtension(pb Message, extension *ExtensionDesc, value interface{}) error { if epb, ok := pb.(extensionsBytes); ok { + ClearExtension(pb, extension) newb, err := encodeExtension(extension, value) if err != nil { return err diff --git a/vendor/github.com/gogo/protobuf/proto/extensions_gogo.go b/vendor/github.com/gogo/protobuf/proto/extensions_gogo.go index 53ebd8cca01c..6f1ae120ece5 100644 --- a/vendor/github.com/gogo/protobuf/proto/extensions_gogo.go +++ b/vendor/github.com/gogo/protobuf/proto/extensions_gogo.go @@ -154,6 +154,10 @@ func EncodeInternalExtension(m extendableProto, data []byte) (n int, err error) return EncodeExtensionMap(m.extensionsWrite(), data) } +func EncodeInternalExtensionBackwards(m extendableProto, data []byte) (n int, err error) { + return EncodeExtensionMapBackwards(m.extensionsWrite(), data) +} + func EncodeExtensionMap(m map[int32]Extension, data []byte) (n int, err error) { o := 0 for _, e := range m { @@ -169,6 +173,23 @@ func EncodeExtensionMap(m map[int32]Extension, data []byte) (n int, err error) { return o, nil } +func EncodeExtensionMapBackwards(m map[int32]Extension, data []byte) (n int, err error) { + o := 0 + end := len(data) + for _, e := range m { + if err := e.Encode(); err != nil { + return 0, err + } + n := copy(data[end-len(e.enc):], e.enc) + if n != len(e.enc) { + return 0, io.ErrShortBuffer + } + end -= n + o += n + } + return o, nil +} + func GetRawExtension(m map[int32]Extension, id int32) ([]byte, error) { e := m[id] if err := e.Encode(); err != nil { diff --git a/vendor/github.com/gogo/protobuf/proto/table_merge.go b/vendor/github.com/gogo/protobuf/proto/table_merge.go index f520106e09f5..60dcf70d1e6c 100644 --- a/vendor/github.com/gogo/protobuf/proto/table_merge.go +++ b/vendor/github.com/gogo/protobuf/proto/table_merge.go @@ -530,6 +530,25 @@ func (mi *mergeInfo) computeMergeInfo() { } case reflect.Struct: switch { + case isSlice && !isPointer: // E.g. []pb.T + mergeInfo := getMergeInfo(tf) + zero := reflect.Zero(tf) + mfi.merge = func(dst, src pointer) { + // TODO: Make this faster? + dstsp := dst.asPointerTo(f.Type) + dsts := dstsp.Elem() + srcs := src.asPointerTo(f.Type).Elem() + for i := 0; i < srcs.Len(); i++ { + dsts = reflect.Append(dsts, zero) + srcElement := srcs.Index(i).Addr() + dstElement := dsts.Index(dsts.Len() - 1).Addr() + mergeInfo.merge(valToPointer(dstElement), valToPointer(srcElement)) + } + if dsts.IsNil() { + dsts = reflect.MakeSlice(f.Type, 0, 0) + } + dstsp.Elem().Set(dsts) + } case !isPointer: mergeInfo := getMergeInfo(tf) mfi.merge = func(dst, src pointer) { diff --git a/vendor/github.com/gogo/protobuf/types/any.pb.go b/vendor/github.com/gogo/protobuf/types/any.pb.go index 202fe8e00b05..4492d8205b01 100644 --- a/vendor/github.com/gogo/protobuf/types/any.pb.go +++ b/vendor/github.com/gogo/protobuf/types/any.pb.go @@ -9,6 +9,7 @@ import ( proto "github.com/gogo/protobuf/proto" io "io" math "math" + math_bits "math/bits" reflect "reflect" strings "strings" ) @@ -155,7 +156,7 @@ func (m *Any) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Any.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -309,7 +310,7 @@ func valueToGoStringAny(v interface{}, typ string) string { func (m *Any) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -317,36 +318,46 @@ func (m *Any) Marshal() (dAtA []byte, err error) { } func (m *Any) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Any) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l - if len(m.TypeUrl) > 0 { - dAtA[i] = 0xa - i++ - i = encodeVarintAny(dAtA, i, uint64(len(m.TypeUrl))) - i += copy(dAtA[i:], m.TypeUrl) + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) } if len(m.Value) > 0 { - dAtA[i] = 0x12 - i++ + i -= len(m.Value) + copy(dAtA[i:], m.Value) i = encodeVarintAny(dAtA, i, uint64(len(m.Value))) - i += copy(dAtA[i:], m.Value) + i-- + dAtA[i] = 0x12 } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + if len(m.TypeUrl) > 0 { + i -= len(m.TypeUrl) + copy(dAtA[i:], m.TypeUrl) + i = encodeVarintAny(dAtA, i, uint64(len(m.TypeUrl))) + i-- + dAtA[i] = 0xa } - return i, nil + return len(dAtA) - i, nil } func encodeVarintAny(dAtA []byte, offset int, v uint64) int { + offset -= sovAny(v) + base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) - return offset + 1 + return base } func NewPopulatedAny(r randyAny, easy bool) *Any { this := &Any{} @@ -455,14 +466,7 @@ func (m *Any) Size() (n int) { } func sovAny(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n + return (math_bits.Len64(x|1) + 6) / 7 } func sozAny(x uint64) (n int) { return sovAny(uint64((x << 1) ^ uint64((int64(x) >> 63)))) diff --git a/vendor/github.com/gogo/protobuf/types/api.pb.go b/vendor/github.com/gogo/protobuf/types/api.pb.go index fe0eefd2d8c7..b09e3ffd164a 100644 --- a/vendor/github.com/gogo/protobuf/types/api.pb.go +++ b/vendor/github.com/gogo/protobuf/types/api.pb.go @@ -9,6 +9,7 @@ import ( proto "github.com/gogo/protobuf/proto" io "io" math "math" + math_bits "math/bits" reflect "reflect" strings "strings" ) @@ -88,7 +89,7 @@ func (m *Api) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Api.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -194,7 +195,7 @@ func (m *Method) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Method.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -368,7 +369,7 @@ func (m *Mixin) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Mixin.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -862,7 +863,7 @@ func valueToGoStringApi(v interface{}, typ string) string { func (m *Api) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -870,83 +871,99 @@ func (m *Api) Marshal() (dAtA []byte, err error) { } func (m *Api) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Api) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l - if len(m.Name) > 0 { - dAtA[i] = 0xa - i++ - i = encodeVarintApi(dAtA, i, uint64(len(m.Name))) - i += copy(dAtA[i:], m.Name) + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) } - if len(m.Methods) > 0 { - for _, msg := range m.Methods { - dAtA[i] = 0x12 - i++ - i = encodeVarintApi(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + if m.Syntax != 0 { + i = encodeVarintApi(dAtA, i, uint64(m.Syntax)) + i-- + dAtA[i] = 0x38 + } + if len(m.Mixins) > 0 { + for iNdEx := len(m.Mixins) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Mixins[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintApi(dAtA, i, uint64(size)) } - i += n + i-- + dAtA[i] = 0x32 } } - if len(m.Options) > 0 { - for _, msg := range m.Options { - dAtA[i] = 0x1a - i++ - i = encodeVarintApi(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) + if m.SourceContext != nil { + { + size, err := m.SourceContext.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } - i += n + i -= size + i = encodeVarintApi(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0x2a } if len(m.Version) > 0 { - dAtA[i] = 0x22 - i++ + i -= len(m.Version) + copy(dAtA[i:], m.Version) i = encodeVarintApi(dAtA, i, uint64(len(m.Version))) - i += copy(dAtA[i:], m.Version) + i-- + dAtA[i] = 0x22 } - if m.SourceContext != nil { - dAtA[i] = 0x2a - i++ - i = encodeVarintApi(dAtA, i, uint64(m.SourceContext.Size())) - n1, err := m.SourceContext.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + if len(m.Options) > 0 { + for iNdEx := len(m.Options) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Options[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintApi(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a } - i += n1 } - if len(m.Mixins) > 0 { - for _, msg := range m.Mixins { - dAtA[i] = 0x32 - i++ - i = encodeVarintApi(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + if len(m.Methods) > 0 { + for iNdEx := len(m.Methods) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Methods[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintApi(dAtA, i, uint64(size)) } - i += n + i-- + dAtA[i] = 0x12 } } - if m.Syntax != 0 { - dAtA[i] = 0x38 - i++ - i = encodeVarintApi(dAtA, i, uint64(m.Syntax)) - } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintApi(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa } - return i, nil + return len(dAtA) - i, nil } func (m *Method) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -954,75 +971,86 @@ func (m *Method) Marshal() (dAtA []byte, err error) { } func (m *Method) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Method) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l - if len(m.Name) > 0 { - dAtA[i] = 0xa - i++ - i = encodeVarintApi(dAtA, i, uint64(len(m.Name))) - i += copy(dAtA[i:], m.Name) + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) } - if len(m.RequestTypeUrl) > 0 { - dAtA[i] = 0x12 - i++ - i = encodeVarintApi(dAtA, i, uint64(len(m.RequestTypeUrl))) - i += copy(dAtA[i:], m.RequestTypeUrl) + if m.Syntax != 0 { + i = encodeVarintApi(dAtA, i, uint64(m.Syntax)) + i-- + dAtA[i] = 0x38 } - if m.RequestStreaming { - dAtA[i] = 0x18 - i++ - if m.RequestStreaming { + if len(m.Options) > 0 { + for iNdEx := len(m.Options) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Options[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintApi(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } + if m.ResponseStreaming { + i-- + if m.ResponseStreaming { dAtA[i] = 1 } else { dAtA[i] = 0 } - i++ + i-- + dAtA[i] = 0x28 } if len(m.ResponseTypeUrl) > 0 { - dAtA[i] = 0x22 - i++ + i -= len(m.ResponseTypeUrl) + copy(dAtA[i:], m.ResponseTypeUrl) i = encodeVarintApi(dAtA, i, uint64(len(m.ResponseTypeUrl))) - i += copy(dAtA[i:], m.ResponseTypeUrl) + i-- + dAtA[i] = 0x22 } - if m.ResponseStreaming { - dAtA[i] = 0x28 - i++ - if m.ResponseStreaming { + if m.RequestStreaming { + i-- + if m.RequestStreaming { dAtA[i] = 1 } else { dAtA[i] = 0 } - i++ - } - if len(m.Options) > 0 { - for _, msg := range m.Options { - dAtA[i] = 0x32 - i++ - i = encodeVarintApi(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } + i-- + dAtA[i] = 0x18 } - if m.Syntax != 0 { - dAtA[i] = 0x38 - i++ - i = encodeVarintApi(dAtA, i, uint64(m.Syntax)) + if len(m.RequestTypeUrl) > 0 { + i -= len(m.RequestTypeUrl) + copy(dAtA[i:], m.RequestTypeUrl) + i = encodeVarintApi(dAtA, i, uint64(len(m.RequestTypeUrl))) + i-- + dAtA[i] = 0x12 } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintApi(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa } - return i, nil + return len(dAtA) - i, nil } func (m *Mixin) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1030,48 +1058,58 @@ func (m *Mixin) Marshal() (dAtA []byte, err error) { } func (m *Mixin) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Mixin) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l - if len(m.Name) > 0 { - dAtA[i] = 0xa - i++ - i = encodeVarintApi(dAtA, i, uint64(len(m.Name))) - i += copy(dAtA[i:], m.Name) + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) } if len(m.Root) > 0 { - dAtA[i] = 0x12 - i++ + i -= len(m.Root) + copy(dAtA[i:], m.Root) i = encodeVarintApi(dAtA, i, uint64(len(m.Root))) - i += copy(dAtA[i:], m.Root) + i-- + dAtA[i] = 0x12 } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintApi(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa } - return i, nil + return len(dAtA) - i, nil } func encodeVarintApi(dAtA []byte, offset int, v uint64) int { + offset -= sovApi(v) + base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) - return offset + 1 + return base } func NewPopulatedApi(r randyApi, easy bool) *Api { this := &Api{} this.Name = string(randStringApi(r)) - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { v1 := r.Intn(5) this.Methods = make([]*Method, v1) for i := 0; i < v1; i++ { this.Methods[i] = NewPopulatedMethod(r, easy) } } - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { v2 := r.Intn(5) this.Options = make([]*Option, v2) for i := 0; i < v2; i++ { @@ -1079,10 +1117,10 @@ func NewPopulatedApi(r randyApi, easy bool) *Api { } } this.Version = string(randStringApi(r)) - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { this.SourceContext = NewPopulatedSourceContext(r, easy) } - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { v3 := r.Intn(5) this.Mixins = make([]*Mixin, v3) for i := 0; i < v3; i++ { @@ -1103,7 +1141,7 @@ func NewPopulatedMethod(r randyApi, easy bool) *Method { this.RequestStreaming = bool(bool(r.Intn(2) == 0)) this.ResponseTypeUrl = string(randStringApi(r)) this.ResponseStreaming = bool(bool(r.Intn(2) == 0)) - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { v4 := r.Intn(5) this.Options = make([]*Option, v4) for i := 0; i < v4; i++ { @@ -1304,14 +1342,7 @@ func (m *Mixin) Size() (n int) { } func sovApi(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n + return (math_bits.Len64(x|1) + 6) / 7 } func sozApi(x uint64) (n int) { return sovApi(uint64((x << 1) ^ uint64((int64(x) >> 63)))) @@ -1320,13 +1351,28 @@ func (this *Api) String() string { if this == nil { return "nil" } + repeatedStringForMethods := "[]*Method{" + for _, f := range this.Methods { + repeatedStringForMethods += strings.Replace(f.String(), "Method", "Method", 1) + "," + } + repeatedStringForMethods += "}" + repeatedStringForOptions := "[]*Option{" + for _, f := range this.Options { + repeatedStringForOptions += strings.Replace(fmt.Sprintf("%v", f), "Option", "Option", 1) + "," + } + repeatedStringForOptions += "}" + repeatedStringForMixins := "[]*Mixin{" + for _, f := range this.Mixins { + repeatedStringForMixins += strings.Replace(f.String(), "Mixin", "Mixin", 1) + "," + } + repeatedStringForMixins += "}" s := strings.Join([]string{`&Api{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Methods:` + strings.Replace(fmt.Sprintf("%v", this.Methods), "Method", "Method", 1) + `,`, - `Options:` + strings.Replace(fmt.Sprintf("%v", this.Options), "Option", "Option", 1) + `,`, + `Methods:` + repeatedStringForMethods + `,`, + `Options:` + repeatedStringForOptions + `,`, `Version:` + fmt.Sprintf("%v", this.Version) + `,`, `SourceContext:` + strings.Replace(fmt.Sprintf("%v", this.SourceContext), "SourceContext", "SourceContext", 1) + `,`, - `Mixins:` + strings.Replace(fmt.Sprintf("%v", this.Mixins), "Mixin", "Mixin", 1) + `,`, + `Mixins:` + repeatedStringForMixins + `,`, `Syntax:` + fmt.Sprintf("%v", this.Syntax) + `,`, `XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`, `}`, @@ -1337,13 +1383,18 @@ func (this *Method) String() string { if this == nil { return "nil" } + repeatedStringForOptions := "[]*Option{" + for _, f := range this.Options { + repeatedStringForOptions += strings.Replace(fmt.Sprintf("%v", f), "Option", "Option", 1) + "," + } + repeatedStringForOptions += "}" s := strings.Join([]string{`&Method{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `RequestTypeUrl:` + fmt.Sprintf("%v", this.RequestTypeUrl) + `,`, `RequestStreaming:` + fmt.Sprintf("%v", this.RequestStreaming) + `,`, `ResponseTypeUrl:` + fmt.Sprintf("%v", this.ResponseTypeUrl) + `,`, `ResponseStreaming:` + fmt.Sprintf("%v", this.ResponseStreaming) + `,`, - `Options:` + strings.Replace(fmt.Sprintf("%v", this.Options), "Option", "Option", 1) + `,`, + `Options:` + repeatedStringForOptions + `,`, `Syntax:` + fmt.Sprintf("%v", this.Syntax) + `,`, `XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`, `}`, diff --git a/vendor/github.com/gogo/protobuf/types/duration.pb.go b/vendor/github.com/gogo/protobuf/types/duration.pb.go index f328ee0e2917..a8a11b81f912 100644 --- a/vendor/github.com/gogo/protobuf/types/duration.pb.go +++ b/vendor/github.com/gogo/protobuf/types/duration.pb.go @@ -9,6 +9,7 @@ import ( proto "github.com/gogo/protobuf/proto" io "io" math "math" + math_bits "math/bits" reflect "reflect" strings "strings" ) @@ -115,7 +116,7 @@ func (m *Duration) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Duration.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -272,7 +273,7 @@ func valueToGoStringDuration(v interface{}, typ string) string { func (m *Duration) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -280,34 +281,42 @@ func (m *Duration) Marshal() (dAtA []byte, err error) { } func (m *Duration) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Duration) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l - if m.Seconds != 0 { - dAtA[i] = 0x8 - i++ - i = encodeVarintDuration(dAtA, i, uint64(m.Seconds)) + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) } if m.Nanos != 0 { - dAtA[i] = 0x10 - i++ i = encodeVarintDuration(dAtA, i, uint64(m.Nanos)) + i-- + dAtA[i] = 0x10 } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + if m.Seconds != 0 { + i = encodeVarintDuration(dAtA, i, uint64(m.Seconds)) + i-- + dAtA[i] = 0x8 } - return i, nil + return len(dAtA) - i, nil } func encodeVarintDuration(dAtA []byte, offset int, v uint64) int { + offset -= sovDuration(v) + base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) - return offset + 1 + return base } func (m *Duration) Size() (n int) { if m == nil { @@ -328,14 +337,7 @@ func (m *Duration) Size() (n int) { } func sovDuration(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n + return (math_bits.Len64(x|1) + 6) / 7 } func sozDuration(x uint64) (n int) { return sovDuration(uint64((x << 1) ^ uint64((int64(x) >> 63)))) diff --git a/vendor/github.com/gogo/protobuf/types/empty.pb.go b/vendor/github.com/gogo/protobuf/types/empty.pb.go index 85881878f655..560ed03cd98b 100644 --- a/vendor/github.com/gogo/protobuf/types/empty.pb.go +++ b/vendor/github.com/gogo/protobuf/types/empty.pb.go @@ -9,6 +9,7 @@ import ( proto "github.com/gogo/protobuf/proto" io "io" math "math" + math_bits "math/bits" reflect "reflect" strings "strings" ) @@ -53,7 +54,7 @@ func (m *Empty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Empty.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -173,7 +174,7 @@ func valueToGoStringEmpty(v interface{}, typ string) string { func (m *Empty) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -181,24 +182,32 @@ func (m *Empty) Marshal() (dAtA []byte, err error) { } func (m *Empty) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Empty) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) } - return i, nil + return len(dAtA) - i, nil } func encodeVarintEmpty(dAtA []byte, offset int, v uint64) int { + offset -= sovEmpty(v) + base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) - return offset + 1 + return base } func NewPopulatedEmpty(r randyEmpty, easy bool) *Empty { this := &Empty{} @@ -293,14 +302,7 @@ func (m *Empty) Size() (n int) { } func sovEmpty(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n + return (math_bits.Len64(x|1) + 6) / 7 } func sozEmpty(x uint64) (n int) { return sovEmpty(uint64((x << 1) ^ uint64((int64(x) >> 63)))) diff --git a/vendor/github.com/gogo/protobuf/types/field_mask.pb.go b/vendor/github.com/gogo/protobuf/types/field_mask.pb.go index b401a2b3f36b..b2e5f5d8c898 100644 --- a/vendor/github.com/gogo/protobuf/types/field_mask.pb.go +++ b/vendor/github.com/gogo/protobuf/types/field_mask.pb.go @@ -9,6 +9,7 @@ import ( proto "github.com/gogo/protobuf/proto" io "io" math "math" + math_bits "math/bits" reflect "reflect" strings "strings" ) @@ -244,7 +245,7 @@ func (m *FieldMask) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_FieldMask.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -396,7 +397,7 @@ func valueToGoStringFieldMask(v interface{}, typ string) string { func (m *FieldMask) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -404,39 +405,41 @@ func (m *FieldMask) Marshal() (dAtA []byte, err error) { } func (m *FieldMask) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FieldMask) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if len(m.Paths) > 0 { - for _, s := range m.Paths { + for iNdEx := len(m.Paths) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Paths[iNdEx]) + copy(dAtA[i:], m.Paths[iNdEx]) + i = encodeVarintFieldMask(dAtA, i, uint64(len(m.Paths[iNdEx]))) + i-- dAtA[i] = 0xa - i++ - l = len(s) - for l >= 1<<7 { - dAtA[i] = uint8(uint64(l)&0x7f | 0x80) - l >>= 7 - i++ - } - dAtA[i] = uint8(l) - i++ - i += copy(dAtA[i:], s) } } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) - } - return i, nil + return len(dAtA) - i, nil } func encodeVarintFieldMask(dAtA []byte, offset int, v uint64) int { + offset -= sovFieldMask(v) + base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) - return offset + 1 + return base } func NewPopulatedFieldMask(r randyFieldMask, easy bool) *FieldMask { this := &FieldMask{} @@ -542,14 +545,7 @@ func (m *FieldMask) Size() (n int) { } func sovFieldMask(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n + return (math_bits.Len64(x|1) + 6) / 7 } func sozFieldMask(x uint64) (n int) { return sovFieldMask(uint64((x << 1) ^ uint64((int64(x) >> 63)))) diff --git a/vendor/github.com/gogo/protobuf/types/source_context.pb.go b/vendor/github.com/gogo/protobuf/types/source_context.pb.go index 3688840b5313..cf01b4c0717d 100644 --- a/vendor/github.com/gogo/protobuf/types/source_context.pb.go +++ b/vendor/github.com/gogo/protobuf/types/source_context.pb.go @@ -9,6 +9,7 @@ import ( proto "github.com/gogo/protobuf/proto" io "io" math "math" + math_bits "math/bits" reflect "reflect" strings "strings" ) @@ -48,7 +49,7 @@ func (m *SourceContext) XXX_Marshal(b []byte, deterministic bool) ([]byte, error return xxx_messageInfo_SourceContext.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -190,7 +191,7 @@ func valueToGoStringSourceContext(v interface{}, typ string) string { func (m *SourceContext) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -198,30 +199,39 @@ func (m *SourceContext) Marshal() (dAtA []byte, err error) { } func (m *SourceContext) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SourceContext) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if len(m.FileName) > 0 { - dAtA[i] = 0xa - i++ + i -= len(m.FileName) + copy(dAtA[i:], m.FileName) i = encodeVarintSourceContext(dAtA, i, uint64(len(m.FileName))) - i += copy(dAtA[i:], m.FileName) - } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + i-- + dAtA[i] = 0xa } - return i, nil + return len(dAtA) - i, nil } func encodeVarintSourceContext(dAtA []byte, offset int, v uint64) int { + offset -= sovSourceContext(v) + base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) - return offset + 1 + return base } func NewPopulatedSourceContext(r randySourceContext, easy bool) *SourceContext { this := &SourceContext{} @@ -321,14 +331,7 @@ func (m *SourceContext) Size() (n int) { } func sovSourceContext(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n + return (math_bits.Len64(x|1) + 6) / 7 } func sozSourceContext(x uint64) (n int) { return sovSourceContext(uint64((x << 1) ^ uint64((int64(x) >> 63)))) diff --git a/vendor/github.com/gogo/protobuf/types/struct.pb.go b/vendor/github.com/gogo/protobuf/types/struct.pb.go index 63fd17b03e5e..261f12fc0dce 100644 --- a/vendor/github.com/gogo/protobuf/types/struct.pb.go +++ b/vendor/github.com/gogo/protobuf/types/struct.pb.go @@ -11,6 +11,7 @@ import ( github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys" io "io" math "math" + math_bits "math/bits" reflect "reflect" strconv "strconv" strings "strings" @@ -82,7 +83,7 @@ func (m *Struct) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Struct.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -148,7 +149,7 @@ func (m *Value) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Value.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -413,7 +414,7 @@ func (m *ListValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_ListValue.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -846,7 +847,7 @@ func valueToGoStringStruct(v interface{}, typ string) string { func (m *Struct) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -854,48 +855,52 @@ func (m *Struct) Marshal() (dAtA []byte, err error) { } func (m *Struct) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Struct) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if len(m.Fields) > 0 { for k := range m.Fields { - dAtA[i] = 0xa - i++ v := m.Fields[k] - msgSize := 0 + baseI := i if v != nil { - msgSize = v.Size() - msgSize += 1 + sovStruct(uint64(msgSize)) - } - mapSize := 1 + len(k) + sovStruct(uint64(len(k))) + msgSize - i = encodeVarintStruct(dAtA, i, uint64(mapSize)) - dAtA[i] = 0xa - i++ - i = encodeVarintStruct(dAtA, i, uint64(len(k))) - i += copy(dAtA[i:], k) - if v != nil { - dAtA[i] = 0x12 - i++ - i = encodeVarintStruct(dAtA, i, uint64(v.Size())) - n1, err := v.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + { + size, err := v.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintStruct(dAtA, i, uint64(size)) } - i += n1 + i-- + dAtA[i] = 0x12 } + i -= len(k) + copy(dAtA[i:], k) + i = encodeVarintStruct(dAtA, i, uint64(len(k))) + i-- + dAtA[i] = 0xa + i = encodeVarintStruct(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0xa } } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) - } - return i, nil + return len(dAtA) - i, nil } func (m *Value) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -903,90 +908,127 @@ func (m *Value) Marshal() (dAtA []byte, err error) { } func (m *Value) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Value) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if m.Kind != nil { - nn2, err := m.Kind.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + { + size := m.Kind.Size() + i -= size + if _, err := m.Kind.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } } - i += nn2 - } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) } - return i, nil + return len(dAtA) - i, nil } func (m *Value_NullValue) MarshalTo(dAtA []byte) (int, error) { - i := 0 - dAtA[i] = 0x8 - i++ + return m.MarshalToSizedBuffer(dAtA[:m.Size()]) +} + +func (m *Value_NullValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) i = encodeVarintStruct(dAtA, i, uint64(m.NullValue)) - return i, nil + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil } func (m *Value_NumberValue) MarshalTo(dAtA []byte) (int, error) { - i := 0 - dAtA[i] = 0x11 - i++ + return m.MarshalToSizedBuffer(dAtA[:m.Size()]) +} + +func (m *Value_NumberValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i -= 8 encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.NumberValue)))) - i += 8 - return i, nil + i-- + dAtA[i] = 0x11 + return len(dAtA) - i, nil } func (m *Value_StringValue) MarshalTo(dAtA []byte) (int, error) { - i := 0 - dAtA[i] = 0x1a - i++ + return m.MarshalToSizedBuffer(dAtA[:m.Size()]) +} + +func (m *Value_StringValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i -= len(m.StringValue) + copy(dAtA[i:], m.StringValue) i = encodeVarintStruct(dAtA, i, uint64(len(m.StringValue))) - i += copy(dAtA[i:], m.StringValue) - return i, nil + i-- + dAtA[i] = 0x1a + return len(dAtA) - i, nil } func (m *Value_BoolValue) MarshalTo(dAtA []byte) (int, error) { - i := 0 - dAtA[i] = 0x20 - i++ + return m.MarshalToSizedBuffer(dAtA[:m.Size()]) +} + +func (m *Value_BoolValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i-- if m.BoolValue { dAtA[i] = 1 } else { dAtA[i] = 0 } - i++ - return i, nil + i-- + dAtA[i] = 0x20 + return len(dAtA) - i, nil } func (m *Value_StructValue) MarshalTo(dAtA []byte) (int, error) { - i := 0 + return m.MarshalToSizedBuffer(dAtA[:m.Size()]) +} + +func (m *Value_StructValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) if m.StructValue != nil { - dAtA[i] = 0x2a - i++ - i = encodeVarintStruct(dAtA, i, uint64(m.StructValue.Size())) - n3, err := m.StructValue.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + { + size, err := m.StructValue.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintStruct(dAtA, i, uint64(size)) } - i += n3 + i-- + dAtA[i] = 0x2a } - return i, nil + return len(dAtA) - i, nil } func (m *Value_ListValue) MarshalTo(dAtA []byte) (int, error) { - i := 0 + return m.MarshalToSizedBuffer(dAtA[:m.Size()]) +} + +func (m *Value_ListValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) if m.ListValue != nil { - dAtA[i] = 0x32 - i++ - i = encodeVarintStruct(dAtA, i, uint64(m.ListValue.Size())) - n4, err := m.ListValue.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + { + size, err := m.ListValue.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintStruct(dAtA, i, uint64(size)) } - i += n4 + i-- + dAtA[i] = 0x32 } - return i, nil + return len(dAtA) - i, nil } func (m *ListValue) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -994,40 +1036,50 @@ func (m *ListValue) Marshal() (dAtA []byte, err error) { } func (m *ListValue) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ListValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if len(m.Values) > 0 { - for _, msg := range m.Values { - dAtA[i] = 0xa - i++ - i = encodeVarintStruct(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + for iNdEx := len(m.Values) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Values[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintStruct(dAtA, i, uint64(size)) } - i += n + i-- + dAtA[i] = 0xa } } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) - } - return i, nil + return len(dAtA) - i, nil } func encodeVarintStruct(dAtA []byte, offset int, v uint64) int { + offset -= sovStruct(v) + base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) - return offset + 1 + return base } func NewPopulatedStruct(r randyStruct, easy bool) *Struct { this := &Struct{} - if r.Intn(10) == 0 { + if r.Intn(5) == 0 { v1 := r.Intn(10) this.Fields = make(map[string]*Value) for i := 0; i < v1; i++ { @@ -1098,7 +1150,7 @@ func NewPopulatedValue_ListValue(r randyStruct, easy bool) *Value_ListValue { } func NewPopulatedListValue(r randyStruct, easy bool) *ListValue { this := &ListValue{} - if r.Intn(10) == 0 { + if r.Intn(5) == 0 { v2 := r.Intn(5) this.Values = make([]*Value, v2) for i := 0; i < v2; i++ { @@ -1303,14 +1355,7 @@ func (m *ListValue) Size() (n int) { } func sovStruct(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n + return (math_bits.Len64(x|1) + 6) / 7 } func sozStruct(x uint64) (n int) { return sovStruct(uint64((x << 1) ^ uint64((int64(x) >> 63)))) @@ -1411,8 +1456,13 @@ func (this *ListValue) String() string { if this == nil { return "nil" } + repeatedStringForValues := "[]*Value{" + for _, f := range this.Values { + repeatedStringForValues += strings.Replace(f.String(), "Value", "Value", 1) + "," + } + repeatedStringForValues += "}" s := strings.Join([]string{`&ListValue{`, - `Values:` + strings.Replace(fmt.Sprintf("%v", this.Values), "Value", "Value", 1) + `,`, + `Values:` + repeatedStringForValues + `,`, `XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`, `}`, }, "") diff --git a/vendor/github.com/gogo/protobuf/types/timestamp.pb.go b/vendor/github.com/gogo/protobuf/types/timestamp.pb.go index 3ee6cb0a1760..928d2511a11a 100644 --- a/vendor/github.com/gogo/protobuf/types/timestamp.pb.go +++ b/vendor/github.com/gogo/protobuf/types/timestamp.pb.go @@ -9,6 +9,7 @@ import ( proto "github.com/gogo/protobuf/proto" io "io" math "math" + math_bits "math/bits" reflect "reflect" strings "strings" ) @@ -135,7 +136,7 @@ func (m *Timestamp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Timestamp.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -292,7 +293,7 @@ func valueToGoStringTimestamp(v interface{}, typ string) string { func (m *Timestamp) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -300,34 +301,42 @@ func (m *Timestamp) Marshal() (dAtA []byte, err error) { } func (m *Timestamp) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Timestamp) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l - if m.Seconds != 0 { - dAtA[i] = 0x8 - i++ - i = encodeVarintTimestamp(dAtA, i, uint64(m.Seconds)) + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) } if m.Nanos != 0 { - dAtA[i] = 0x10 - i++ i = encodeVarintTimestamp(dAtA, i, uint64(m.Nanos)) + i-- + dAtA[i] = 0x10 } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + if m.Seconds != 0 { + i = encodeVarintTimestamp(dAtA, i, uint64(m.Seconds)) + i-- + dAtA[i] = 0x8 } - return i, nil + return len(dAtA) - i, nil } func encodeVarintTimestamp(dAtA []byte, offset int, v uint64) int { + offset -= sovTimestamp(v) + base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) - return offset + 1 + return base } func (m *Timestamp) Size() (n int) { if m == nil { @@ -348,14 +357,7 @@ func (m *Timestamp) Size() (n int) { } func sovTimestamp(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n + return (math_bits.Len64(x|1) + 6) / 7 } func sozTimestamp(x uint64) (n int) { return sovTimestamp(uint64((x << 1) ^ uint64((int64(x) >> 63)))) diff --git a/vendor/github.com/gogo/protobuf/types/type.pb.go b/vendor/github.com/gogo/protobuf/types/type.pb.go index 366f493d28fe..e0adc52b2fb7 100644 --- a/vendor/github.com/gogo/protobuf/types/type.pb.go +++ b/vendor/github.com/gogo/protobuf/types/type.pb.go @@ -9,6 +9,7 @@ import ( proto "github.com/gogo/protobuf/proto" io "io" math "math" + math_bits "math/bits" reflect "reflect" strconv "strconv" strings "strings" @@ -205,7 +206,7 @@ func (m *Type) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Type.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -312,7 +313,7 @@ func (m *Field) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Field.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -435,7 +436,7 @@ func (m *Enum) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Enum.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -519,7 +520,7 @@ func (m *EnumValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_EnumValue.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -594,7 +595,7 @@ func (m *Option) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Option.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -1404,7 +1405,7 @@ func valueToGoStringType(v interface{}, typ string) string { func (m *Type) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1412,80 +1413,87 @@ func (m *Type) Marshal() (dAtA []byte, err error) { } func (m *Type) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Type) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l - if len(m.Name) > 0 { - dAtA[i] = 0xa - i++ - i = encodeVarintType(dAtA, i, uint64(len(m.Name))) - i += copy(dAtA[i:], m.Name) + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) } - if len(m.Fields) > 0 { - for _, msg := range m.Fields { - dAtA[i] = 0x12 - i++ - i = encodeVarintType(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) + if m.Syntax != 0 { + i = encodeVarintType(dAtA, i, uint64(m.Syntax)) + i-- + dAtA[i] = 0x30 + } + if m.SourceContext != nil { + { + size, err := m.SourceContext.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } - i += n - } - } - if len(m.Oneofs) > 0 { - for _, s := range m.Oneofs { - dAtA[i] = 0x1a - i++ - l = len(s) - for l >= 1<<7 { - dAtA[i] = uint8(uint64(l)&0x7f | 0x80) - l >>= 7 - i++ - } - dAtA[i] = uint8(l) - i++ - i += copy(dAtA[i:], s) + i -= size + i = encodeVarintType(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0x2a } if len(m.Options) > 0 { - for _, msg := range m.Options { - dAtA[i] = 0x22 - i++ - i = encodeVarintType(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + for iNdEx := len(m.Options) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Options[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintType(dAtA, i, uint64(size)) } - i += n + i-- + dAtA[i] = 0x22 } } - if m.SourceContext != nil { - dAtA[i] = 0x2a - i++ - i = encodeVarintType(dAtA, i, uint64(m.SourceContext.Size())) - n1, err := m.SourceContext.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + if len(m.Oneofs) > 0 { + for iNdEx := len(m.Oneofs) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Oneofs[iNdEx]) + copy(dAtA[i:], m.Oneofs[iNdEx]) + i = encodeVarintType(dAtA, i, uint64(len(m.Oneofs[iNdEx]))) + i-- + dAtA[i] = 0x1a } - i += n1 } - if m.Syntax != 0 { - dAtA[i] = 0x30 - i++ - i = encodeVarintType(dAtA, i, uint64(m.Syntax)) + if len(m.Fields) > 0 { + for iNdEx := len(m.Fields) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Fields[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintType(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintType(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa } - return i, nil + return len(dAtA) - i, nil } func (m *Field) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1493,86 +1501,98 @@ func (m *Field) Marshal() (dAtA []byte, err error) { } func (m *Field) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Field) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l - if m.Kind != 0 { - dAtA[i] = 0x8 - i++ - i = encodeVarintType(dAtA, i, uint64(m.Kind)) - } - if m.Cardinality != 0 { - dAtA[i] = 0x10 - i++ - i = encodeVarintType(dAtA, i, uint64(m.Cardinality)) - } - if m.Number != 0 { - dAtA[i] = 0x18 - i++ - i = encodeVarintType(dAtA, i, uint64(m.Number)) + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) } - if len(m.Name) > 0 { - dAtA[i] = 0x22 - i++ - i = encodeVarintType(dAtA, i, uint64(len(m.Name))) - i += copy(dAtA[i:], m.Name) + if len(m.DefaultValue) > 0 { + i -= len(m.DefaultValue) + copy(dAtA[i:], m.DefaultValue) + i = encodeVarintType(dAtA, i, uint64(len(m.DefaultValue))) + i-- + dAtA[i] = 0x5a } - if len(m.TypeUrl) > 0 { - dAtA[i] = 0x32 - i++ - i = encodeVarintType(dAtA, i, uint64(len(m.TypeUrl))) - i += copy(dAtA[i:], m.TypeUrl) + if len(m.JsonName) > 0 { + i -= len(m.JsonName) + copy(dAtA[i:], m.JsonName) + i = encodeVarintType(dAtA, i, uint64(len(m.JsonName))) + i-- + dAtA[i] = 0x52 } - if m.OneofIndex != 0 { - dAtA[i] = 0x38 - i++ - i = encodeVarintType(dAtA, i, uint64(m.OneofIndex)) + if len(m.Options) > 0 { + for iNdEx := len(m.Options) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Options[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintType(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + } } if m.Packed { - dAtA[i] = 0x40 - i++ + i-- if m.Packed { dAtA[i] = 1 } else { dAtA[i] = 0 } - i++ + i-- + dAtA[i] = 0x40 } - if len(m.Options) > 0 { - for _, msg := range m.Options { - dAtA[i] = 0x4a - i++ - i = encodeVarintType(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } + if m.OneofIndex != 0 { + i = encodeVarintType(dAtA, i, uint64(m.OneofIndex)) + i-- + dAtA[i] = 0x38 } - if len(m.JsonName) > 0 { - dAtA[i] = 0x52 - i++ - i = encodeVarintType(dAtA, i, uint64(len(m.JsonName))) - i += copy(dAtA[i:], m.JsonName) + if len(m.TypeUrl) > 0 { + i -= len(m.TypeUrl) + copy(dAtA[i:], m.TypeUrl) + i = encodeVarintType(dAtA, i, uint64(len(m.TypeUrl))) + i-- + dAtA[i] = 0x32 } - if len(m.DefaultValue) > 0 { - dAtA[i] = 0x5a - i++ - i = encodeVarintType(dAtA, i, uint64(len(m.DefaultValue))) - i += copy(dAtA[i:], m.DefaultValue) + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintType(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0x22 } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + if m.Number != 0 { + i = encodeVarintType(dAtA, i, uint64(m.Number)) + i-- + dAtA[i] = 0x18 + } + if m.Cardinality != 0 { + i = encodeVarintType(dAtA, i, uint64(m.Cardinality)) + i-- + dAtA[i] = 0x10 + } + if m.Kind != 0 { + i = encodeVarintType(dAtA, i, uint64(m.Kind)) + i-- + dAtA[i] = 0x8 } - return i, nil + return len(dAtA) - i, nil } func (m *Enum) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1580,65 +1600,78 @@ func (m *Enum) Marshal() (dAtA []byte, err error) { } func (m *Enum) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Enum) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l - if len(m.Name) > 0 { - dAtA[i] = 0xa - i++ - i = encodeVarintType(dAtA, i, uint64(len(m.Name))) - i += copy(dAtA[i:], m.Name) + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) } - if len(m.Enumvalue) > 0 { - for _, msg := range m.Enumvalue { - dAtA[i] = 0x12 - i++ - i = encodeVarintType(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) + if m.Syntax != 0 { + i = encodeVarintType(dAtA, i, uint64(m.Syntax)) + i-- + dAtA[i] = 0x28 + } + if m.SourceContext != nil { + { + size, err := m.SourceContext.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } - i += n + i -= size + i = encodeVarintType(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0x22 } if len(m.Options) > 0 { - for _, msg := range m.Options { - dAtA[i] = 0x1a - i++ - i = encodeVarintType(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + for iNdEx := len(m.Options) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Options[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintType(dAtA, i, uint64(size)) } - i += n + i-- + dAtA[i] = 0x1a } } - if m.SourceContext != nil { - dAtA[i] = 0x22 - i++ - i = encodeVarintType(dAtA, i, uint64(m.SourceContext.Size())) - n2, err := m.SourceContext.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + if len(m.Enumvalue) > 0 { + for iNdEx := len(m.Enumvalue) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Enumvalue[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintType(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 } - i += n2 } - if m.Syntax != 0 { - dAtA[i] = 0x28 - i++ - i = encodeVarintType(dAtA, i, uint64(m.Syntax)) - } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintType(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa } - return i, nil + return len(dAtA) - i, nil } func (m *EnumValue) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1646,43 +1679,52 @@ func (m *EnumValue) Marshal() (dAtA []byte, err error) { } func (m *EnumValue) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EnumValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l - if len(m.Name) > 0 { - dAtA[i] = 0xa - i++ - i = encodeVarintType(dAtA, i, uint64(len(m.Name))) - i += copy(dAtA[i:], m.Name) - } - if m.Number != 0 { - dAtA[i] = 0x10 - i++ - i = encodeVarintType(dAtA, i, uint64(m.Number)) + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) } if len(m.Options) > 0 { - for _, msg := range m.Options { - dAtA[i] = 0x1a - i++ - i = encodeVarintType(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + for iNdEx := len(m.Options) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Options[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintType(dAtA, i, uint64(size)) } - i += n + i-- + dAtA[i] = 0x1a } } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + if m.Number != 0 { + i = encodeVarintType(dAtA, i, uint64(m.Number)) + i-- + dAtA[i] = 0x10 + } + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintType(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa } - return i, nil + return len(dAtA) - i, nil } func (m *Option) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1690,45 +1732,56 @@ func (m *Option) Marshal() (dAtA []byte, err error) { } func (m *Option) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Option) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l - if len(m.Name) > 0 { - dAtA[i] = 0xa - i++ - i = encodeVarintType(dAtA, i, uint64(len(m.Name))) - i += copy(dAtA[i:], m.Name) + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) } if m.Value != nil { - dAtA[i] = 0x12 - i++ - i = encodeVarintType(dAtA, i, uint64(m.Value.Size())) - n3, err := m.Value.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err + { + size, err := m.Value.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintType(dAtA, i, uint64(size)) } - i += n3 + i-- + dAtA[i] = 0x12 } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintType(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa } - return i, nil + return len(dAtA) - i, nil } func encodeVarintType(dAtA []byte, offset int, v uint64) int { + offset -= sovType(v) + base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) - return offset + 1 + return base } func NewPopulatedType(r randyType, easy bool) *Type { this := &Type{} this.Name = string(randStringType(r)) - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { v1 := r.Intn(5) this.Fields = make([]*Field, v1) for i := 0; i < v1; i++ { @@ -1740,14 +1793,14 @@ func NewPopulatedType(r randyType, easy bool) *Type { for i := 0; i < v2; i++ { this.Oneofs[i] = string(randStringType(r)) } - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { v3 := r.Intn(5) this.Options = make([]*Option, v3) for i := 0; i < v3; i++ { this.Options[i] = NewPopulatedOption(r, easy) } } - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { this.SourceContext = NewPopulatedSourceContext(r, easy) } this.Syntax = Syntax([]int32{0, 1}[r.Intn(2)]) @@ -1772,7 +1825,7 @@ func NewPopulatedField(r randyType, easy bool) *Field { this.OneofIndex *= -1 } this.Packed = bool(bool(r.Intn(2) == 0)) - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { v4 := r.Intn(5) this.Options = make([]*Option, v4) for i := 0; i < v4; i++ { @@ -1790,21 +1843,21 @@ func NewPopulatedField(r randyType, easy bool) *Field { func NewPopulatedEnum(r randyType, easy bool) *Enum { this := &Enum{} this.Name = string(randStringType(r)) - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { v5 := r.Intn(5) this.Enumvalue = make([]*EnumValue, v5) for i := 0; i < v5; i++ { this.Enumvalue[i] = NewPopulatedEnumValue(r, easy) } } - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { v6 := r.Intn(5) this.Options = make([]*Option, v6) for i := 0; i < v6; i++ { this.Options[i] = NewPopulatedOption(r, easy) } } - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { this.SourceContext = NewPopulatedSourceContext(r, easy) } this.Syntax = Syntax([]int32{0, 1}[r.Intn(2)]) @@ -1821,7 +1874,7 @@ func NewPopulatedEnumValue(r randyType, easy bool) *EnumValue { if r.Intn(2) == 0 { this.Number *= -1 } - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { v7 := r.Intn(5) this.Options = make([]*Option, v7) for i := 0; i < v7; i++ { @@ -1837,7 +1890,7 @@ func NewPopulatedEnumValue(r randyType, easy bool) *EnumValue { func NewPopulatedOption(r randyType, easy bool) *Option { this := &Option{} this.Name = string(randStringType(r)) - if r.Intn(10) != 0 { + if r.Intn(5) != 0 { this.Value = NewPopulatedAny(r, easy) } if !easy && r.Intn(10) != 0 { @@ -2089,14 +2142,7 @@ func (m *Option) Size() (n int) { } func sovType(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n + return (math_bits.Len64(x|1) + 6) / 7 } func sozType(x uint64) (n int) { return sovType(uint64((x << 1) ^ uint64((int64(x) >> 63)))) @@ -2105,11 +2151,21 @@ func (this *Type) String() string { if this == nil { return "nil" } + repeatedStringForFields := "[]*Field{" + for _, f := range this.Fields { + repeatedStringForFields += strings.Replace(f.String(), "Field", "Field", 1) + "," + } + repeatedStringForFields += "}" + repeatedStringForOptions := "[]*Option{" + for _, f := range this.Options { + repeatedStringForOptions += strings.Replace(f.String(), "Option", "Option", 1) + "," + } + repeatedStringForOptions += "}" s := strings.Join([]string{`&Type{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Fields:` + strings.Replace(fmt.Sprintf("%v", this.Fields), "Field", "Field", 1) + `,`, + `Fields:` + repeatedStringForFields + `,`, `Oneofs:` + fmt.Sprintf("%v", this.Oneofs) + `,`, - `Options:` + strings.Replace(fmt.Sprintf("%v", this.Options), "Option", "Option", 1) + `,`, + `Options:` + repeatedStringForOptions + `,`, `SourceContext:` + strings.Replace(fmt.Sprintf("%v", this.SourceContext), "SourceContext", "SourceContext", 1) + `,`, `Syntax:` + fmt.Sprintf("%v", this.Syntax) + `,`, `XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`, @@ -2121,6 +2177,11 @@ func (this *Field) String() string { if this == nil { return "nil" } + repeatedStringForOptions := "[]*Option{" + for _, f := range this.Options { + repeatedStringForOptions += strings.Replace(f.String(), "Option", "Option", 1) + "," + } + repeatedStringForOptions += "}" s := strings.Join([]string{`&Field{`, `Kind:` + fmt.Sprintf("%v", this.Kind) + `,`, `Cardinality:` + fmt.Sprintf("%v", this.Cardinality) + `,`, @@ -2129,7 +2190,7 @@ func (this *Field) String() string { `TypeUrl:` + fmt.Sprintf("%v", this.TypeUrl) + `,`, `OneofIndex:` + fmt.Sprintf("%v", this.OneofIndex) + `,`, `Packed:` + fmt.Sprintf("%v", this.Packed) + `,`, - `Options:` + strings.Replace(fmt.Sprintf("%v", this.Options), "Option", "Option", 1) + `,`, + `Options:` + repeatedStringForOptions + `,`, `JsonName:` + fmt.Sprintf("%v", this.JsonName) + `,`, `DefaultValue:` + fmt.Sprintf("%v", this.DefaultValue) + `,`, `XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`, @@ -2141,10 +2202,20 @@ func (this *Enum) String() string { if this == nil { return "nil" } + repeatedStringForEnumvalue := "[]*EnumValue{" + for _, f := range this.Enumvalue { + repeatedStringForEnumvalue += strings.Replace(f.String(), "EnumValue", "EnumValue", 1) + "," + } + repeatedStringForEnumvalue += "}" + repeatedStringForOptions := "[]*Option{" + for _, f := range this.Options { + repeatedStringForOptions += strings.Replace(f.String(), "Option", "Option", 1) + "," + } + repeatedStringForOptions += "}" s := strings.Join([]string{`&Enum{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `Enumvalue:` + strings.Replace(fmt.Sprintf("%v", this.Enumvalue), "EnumValue", "EnumValue", 1) + `,`, - `Options:` + strings.Replace(fmt.Sprintf("%v", this.Options), "Option", "Option", 1) + `,`, + `Enumvalue:` + repeatedStringForEnumvalue + `,`, + `Options:` + repeatedStringForOptions + `,`, `SourceContext:` + strings.Replace(fmt.Sprintf("%v", this.SourceContext), "SourceContext", "SourceContext", 1) + `,`, `Syntax:` + fmt.Sprintf("%v", this.Syntax) + `,`, `XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`, @@ -2156,10 +2227,15 @@ func (this *EnumValue) String() string { if this == nil { return "nil" } + repeatedStringForOptions := "[]*Option{" + for _, f := range this.Options { + repeatedStringForOptions += strings.Replace(f.String(), "Option", "Option", 1) + "," + } + repeatedStringForOptions += "}" s := strings.Join([]string{`&EnumValue{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `Number:` + fmt.Sprintf("%v", this.Number) + `,`, - `Options:` + strings.Replace(fmt.Sprintf("%v", this.Options), "Option", "Option", 1) + `,`, + `Options:` + repeatedStringForOptions + `,`, `XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`, `}`, }, "") diff --git a/vendor/github.com/gogo/protobuf/types/wrappers.pb.go b/vendor/github.com/gogo/protobuf/types/wrappers.pb.go index 5ade933ef57c..fa1fd7ed3c1c 100644 --- a/vendor/github.com/gogo/protobuf/types/wrappers.pb.go +++ b/vendor/github.com/gogo/protobuf/types/wrappers.pb.go @@ -10,6 +10,7 @@ import ( proto "github.com/gogo/protobuf/proto" io "io" math "math" + math_bits "math/bits" reflect "reflect" strings "strings" ) @@ -50,7 +51,7 @@ func (m *DoubleValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) return xxx_messageInfo_DoubleValue.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -105,7 +106,7 @@ func (m *FloatValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_FloatValue.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -160,7 +161,7 @@ func (m *Int64Value) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Int64Value.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -215,7 +216,7 @@ func (m *UInt64Value) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) return xxx_messageInfo_UInt64Value.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -270,7 +271,7 @@ func (m *Int32Value) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Int32Value.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -325,7 +326,7 @@ func (m *UInt32Value) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) return xxx_messageInfo_UInt32Value.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -380,7 +381,7 @@ func (m *BoolValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_BoolValue.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -435,7 +436,7 @@ func (m *StringValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) return xxx_messageInfo_StringValue.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -490,7 +491,7 @@ func (m *BytesValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_BytesValue.Marshal(b, m, deterministic) } else { b = b[:cap(b)] - n, err := m.MarshalTo(b) + n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } @@ -1247,7 +1248,7 @@ func valueToGoStringWrappers(v interface{}, typ string) string { func (m *DoubleValue) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1255,26 +1256,32 @@ func (m *DoubleValue) Marshal() (dAtA []byte, err error) { } func (m *DoubleValue) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DoubleValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if m.Value != 0 { - dAtA[i] = 0x9 - i++ + i -= 8 encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Value)))) - i += 8 - } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + i-- + dAtA[i] = 0x9 } - return i, nil + return len(dAtA) - i, nil } func (m *FloatValue) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1282,26 +1289,32 @@ func (m *FloatValue) Marshal() (dAtA []byte, err error) { } func (m *FloatValue) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FloatValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if m.Value != 0 { - dAtA[i] = 0xd - i++ + i -= 4 encoding_binary.LittleEndian.PutUint32(dAtA[i:], uint32(math.Float32bits(float32(m.Value)))) - i += 4 - } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + i-- + dAtA[i] = 0xd } - return i, nil + return len(dAtA) - i, nil } func (m *Int64Value) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1309,25 +1322,31 @@ func (m *Int64Value) Marshal() (dAtA []byte, err error) { } func (m *Int64Value) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Int64Value) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if m.Value != 0 { - dAtA[i] = 0x8 - i++ i = encodeVarintWrappers(dAtA, i, uint64(m.Value)) + i-- + dAtA[i] = 0x8 } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) - } - return i, nil + return len(dAtA) - i, nil } func (m *UInt64Value) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1335,25 +1354,31 @@ func (m *UInt64Value) Marshal() (dAtA []byte, err error) { } func (m *UInt64Value) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *UInt64Value) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if m.Value != 0 { - dAtA[i] = 0x8 - i++ i = encodeVarintWrappers(dAtA, i, uint64(m.Value)) + i-- + dAtA[i] = 0x8 } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) - } - return i, nil + return len(dAtA) - i, nil } func (m *Int32Value) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1361,25 +1386,31 @@ func (m *Int32Value) Marshal() (dAtA []byte, err error) { } func (m *Int32Value) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Int32Value) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if m.Value != 0 { - dAtA[i] = 0x8 - i++ i = encodeVarintWrappers(dAtA, i, uint64(m.Value)) + i-- + dAtA[i] = 0x8 } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) - } - return i, nil + return len(dAtA) - i, nil } func (m *UInt32Value) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1387,25 +1418,31 @@ func (m *UInt32Value) Marshal() (dAtA []byte, err error) { } func (m *UInt32Value) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *UInt32Value) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if m.Value != 0 { - dAtA[i] = 0x8 - i++ i = encodeVarintWrappers(dAtA, i, uint64(m.Value)) + i-- + dAtA[i] = 0x8 } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) - } - return i, nil + return len(dAtA) - i, nil } func (m *BoolValue) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1413,30 +1450,36 @@ func (m *BoolValue) Marshal() (dAtA []byte, err error) { } func (m *BoolValue) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BoolValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if m.Value { - dAtA[i] = 0x8 - i++ + i-- if m.Value { dAtA[i] = 1 } else { dAtA[i] = 0 } - i++ - } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + i-- + dAtA[i] = 0x8 } - return i, nil + return len(dAtA) - i, nil } func (m *StringValue) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1444,26 +1487,33 @@ func (m *StringValue) Marshal() (dAtA []byte, err error) { } func (m *StringValue) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *StringValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if len(m.Value) > 0 { - dAtA[i] = 0xa - i++ + i -= len(m.Value) + copy(dAtA[i:], m.Value) i = encodeVarintWrappers(dAtA, i, uint64(len(m.Value))) - i += copy(dAtA[i:], m.Value) - } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + i-- + dAtA[i] = 0xa } - return i, nil + return len(dAtA) - i, nil } func (m *BytesValue) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } @@ -1471,30 +1521,39 @@ func (m *BytesValue) Marshal() (dAtA []byte, err error) { } func (m *BytesValue) MarshalTo(dAtA []byte) (int, error) { - var i int + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BytesValue) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) _ = i var l int _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } if len(m.Value) > 0 { - dAtA[i] = 0xa - i++ + i -= len(m.Value) + copy(dAtA[i:], m.Value) i = encodeVarintWrappers(dAtA, i, uint64(len(m.Value))) - i += copy(dAtA[i:], m.Value) - } - if m.XXX_unrecognized != nil { - i += copy(dAtA[i:], m.XXX_unrecognized) + i-- + dAtA[i] = 0xa } - return i, nil + return len(dAtA) - i, nil } func encodeVarintWrappers(dAtA []byte, offset int, v uint64) int { + offset -= sovWrappers(v) + base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) - return offset + 1 + return base } func NewPopulatedDoubleValue(r randyWrappers, easy bool) *DoubleValue { this := &DoubleValue{} @@ -1803,14 +1862,7 @@ func (m *BytesValue) Size() (n int) { } func sovWrappers(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n + return (math_bits.Len64(x|1) + 6) / 7 } func sozWrappers(x uint64) (n int) { return sovWrappers(uint64((x << 1) ^ uint64((int64(x) >> 63)))) diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go index e86554b92b45..ff8e785d4e88 100644 --- a/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go @@ -11,6 +11,7 @@ import ( "unicode/utf8" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/internal/function" ) // IgnoreFields returns an Option that ignores exported fields of the @@ -112,6 +113,10 @@ func (tf ifaceFilter) filter(p cmp.Path) bool { // In particular, unexported fields within the struct's exported fields // of struct types, including anonymous fields, will not be ignored unless the // type of the field itself is also passed to IgnoreUnexported. +// +// Avoid ignoring unexported fields of a type which you do not control (i.e. a +// type from another repository), as changes to the implementation of such types +// may change how the comparison behaves. Prefer a custom Comparer instead. func IgnoreUnexported(typs ...interface{}) cmp.Option { ux := newUnexportedFilter(typs...) return cmp.FilterPath(ux.filter, cmp.Ignore()) @@ -143,3 +148,60 @@ func isExported(id string) bool { r, _ := utf8.DecodeRuneInString(id) return unicode.IsUpper(r) } + +// IgnoreSliceElements returns an Option that ignores elements of []V. +// The discard function must be of the form "func(T) bool" which is used to +// ignore slice elements of type V, where V is assignable to T. +// Elements are ignored if the function reports true. +func IgnoreSliceElements(discardFunc interface{}) cmp.Option { + vf := reflect.ValueOf(discardFunc) + if !function.IsType(vf.Type(), function.ValuePredicate) || vf.IsNil() { + panic(fmt.Sprintf("invalid discard function: %T", discardFunc)) + } + return cmp.FilterPath(func(p cmp.Path) bool { + si, ok := p.Index(-1).(cmp.SliceIndex) + if !ok { + return false + } + if !si.Type().AssignableTo(vf.Type().In(0)) { + return false + } + vx, vy := si.Values() + if vx.IsValid() && vf.Call([]reflect.Value{vx})[0].Bool() { + return true + } + if vy.IsValid() && vf.Call([]reflect.Value{vy})[0].Bool() { + return true + } + return false + }, cmp.Ignore()) +} + +// IgnoreMapEntries returns an Option that ignores entries of map[K]V. +// The discard function must be of the form "func(T, R) bool" which is used to +// ignore map entries of type K and V, where K and V are assignable to T and R. +// Entries are ignored if the function reports true. +func IgnoreMapEntries(discardFunc interface{}) cmp.Option { + vf := reflect.ValueOf(discardFunc) + if !function.IsType(vf.Type(), function.KeyValuePredicate) || vf.IsNil() { + panic(fmt.Sprintf("invalid discard function: %T", discardFunc)) + } + return cmp.FilterPath(func(p cmp.Path) bool { + mi, ok := p.Index(-1).(cmp.MapIndex) + if !ok { + return false + } + if !mi.Key().Type().AssignableTo(vf.Type().In(0)) || !mi.Type().AssignableTo(vf.Type().In(1)) { + return false + } + k := mi.Key() + vx, vy := mi.Values() + if vx.IsValid() && vf.Call([]reflect.Value{k, vx})[0].Bool() { + return true + } + if vy.IsValid() && vf.Call([]reflect.Value{k, vy})[0].Bool() { + return true + } + return false + }, cmp.Ignore()) +} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go index da17d7469384..3a4804621e93 100644 --- a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go @@ -7,6 +7,7 @@ package cmpopts import ( "fmt" "reflect" + "sort" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/internal/function" @@ -25,13 +26,13 @@ import ( // !less(y, x) for two elements x and y, their relative order is maintained. // // SortSlices can be used in conjunction with EquateEmpty. -func SortSlices(less interface{}) cmp.Option { - vf := reflect.ValueOf(less) +func SortSlices(lessFunc interface{}) cmp.Option { + vf := reflect.ValueOf(lessFunc) if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { - panic(fmt.Sprintf("invalid less function: %T", less)) + panic(fmt.Sprintf("invalid less function: %T", lessFunc)) } ss := sliceSorter{vf.Type().In(0), vf} - return cmp.FilterValues(ss.filter, cmp.Transformer("Sort", ss.sort)) + return cmp.FilterValues(ss.filter, cmp.Transformer("cmpopts.SortSlices", ss.sort)) } type sliceSorter struct { @@ -48,8 +49,8 @@ func (ss sliceSorter) filter(x, y interface{}) bool { } // Check whether the slices are already sorted to avoid an infinite // recursion cycle applying the same transform to itself. - ok1 := sliceIsSorted(x, func(i, j int) bool { return ss.less(vx, i, j) }) - ok2 := sliceIsSorted(y, func(i, j int) bool { return ss.less(vy, i, j) }) + ok1 := sort.SliceIsSorted(x, func(i, j int) bool { return ss.less(vx, i, j) }) + ok2 := sort.SliceIsSorted(y, func(i, j int) bool { return ss.less(vy, i, j) }) return !ok1 || !ok2 } func (ss sliceSorter) sort(x interface{}) interface{} { @@ -58,7 +59,7 @@ func (ss sliceSorter) sort(x interface{}) interface{} { for i := 0; i < src.Len(); i++ { dst.Index(i).Set(src.Index(i)) } - sortSliceStable(dst.Interface(), func(i, j int) bool { return ss.less(dst, i, j) }) + sort.SliceStable(dst.Interface(), func(i, j int) bool { return ss.less(dst, i, j) }) ss.checkSort(dst) return dst.Interface() } @@ -96,13 +97,13 @@ func (ss sliceSorter) less(v reflect.Value, i, j int) bool { // • Total: if x != y, then either less(x, y) or less(y, x) // // SortMaps can be used in conjunction with EquateEmpty. -func SortMaps(less interface{}) cmp.Option { - vf := reflect.ValueOf(less) +func SortMaps(lessFunc interface{}) cmp.Option { + vf := reflect.ValueOf(lessFunc) if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { - panic(fmt.Sprintf("invalid less function: %T", less)) + panic(fmt.Sprintf("invalid less function: %T", lessFunc)) } ms := mapSorter{vf.Type().In(0), vf} - return cmp.FilterValues(ms.filter, cmp.Transformer("Sort", ms.sort)) + return cmp.FilterValues(ms.filter, cmp.Transformer("cmpopts.SortMaps", ms.sort)) } type mapSorter struct { @@ -118,7 +119,10 @@ func (ms mapSorter) filter(x, y interface{}) bool { } func (ms mapSorter) sort(x interface{}) interface{} { src := reflect.ValueOf(x) - outType := mapEntryType(src.Type()) + outType := reflect.StructOf([]reflect.StructField{ + {Name: "K", Type: src.Type().Key()}, + {Name: "V", Type: src.Type().Elem()}, + }) dst := reflect.MakeSlice(reflect.SliceOf(outType), src.Len(), src.Len()) for i, k := range src.MapKeys() { v := reflect.New(outType).Elem() @@ -126,7 +130,7 @@ func (ms mapSorter) sort(x interface{}) interface{} { v.Field(1).Set(src.MapIndex(k)) dst.Index(i).Set(v) } - sortSlice(dst.Interface(), func(i, j int) bool { return ms.less(dst, i, j) }) + sort.Slice(dst.Interface(), func(i, j int) bool { return ms.less(dst, i, j) }) ms.checkSort(dst) return dst.Interface() } @@ -139,8 +143,5 @@ func (ms mapSorter) checkSort(v reflect.Value) { } func (ms mapSorter) less(v reflect.Value, i, j int) bool { vx, vy := v.Index(i).Field(0), v.Index(j).Field(0) - if !hasReflectStructOf { - vx, vy = vx.Elem(), vy.Elem() - } return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool() } diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go deleted file mode 100644 index 839b88ca4020..000000000000 --- a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE.md file. - -// +build !go1.8 - -package cmpopts - -import ( - "reflect" - "sort" -) - -const hasReflectStructOf = false - -func mapEntryType(reflect.Type) reflect.Type { - return reflect.TypeOf(struct{ K, V interface{} }{}) -} - -func sliceIsSorted(slice interface{}, less func(i, j int) bool) bool { - return sort.IsSorted(reflectSliceSorter{reflect.ValueOf(slice), less}) -} -func sortSlice(slice interface{}, less func(i, j int) bool) { - sort.Sort(reflectSliceSorter{reflect.ValueOf(slice), less}) -} -func sortSliceStable(slice interface{}, less func(i, j int) bool) { - sort.Stable(reflectSliceSorter{reflect.ValueOf(slice), less}) -} - -type reflectSliceSorter struct { - slice reflect.Value - less func(i, j int) bool -} - -func (ss reflectSliceSorter) Len() int { - return ss.slice.Len() -} -func (ss reflectSliceSorter) Less(i, j int) bool { - return ss.less(i, j) -} -func (ss reflectSliceSorter) Swap(i, j int) { - vi := ss.slice.Index(i).Interface() - vj := ss.slice.Index(j).Interface() - ss.slice.Index(i).Set(reflect.ValueOf(vj)) - ss.slice.Index(j).Set(reflect.ValueOf(vi)) -} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go deleted file mode 100644 index 8a59c0d38fdd..000000000000 --- a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE.md file. - -// +build go1.8 - -package cmpopts - -import ( - "reflect" - "sort" -) - -const hasReflectStructOf = true - -func mapEntryType(t reflect.Type) reflect.Type { - return reflect.StructOf([]reflect.StructField{ - {Name: "K", Type: t.Key()}, - {Name: "V", Type: t.Elem()}, - }) -} - -func sliceIsSorted(slice interface{}, less func(i, j int) bool) bool { - return sort.SliceIsSorted(slice, less) -} -func sortSlice(slice interface{}, less func(i, j int) bool) { - sort.Slice(slice, less) -} -func sortSliceStable(slice interface{}, less func(i, j int) bool) { - sort.SliceStable(slice, less) -} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/xform.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/xform.go new file mode 100644 index 000000000000..9d651553d78a --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/xform.go @@ -0,0 +1,35 @@ +// Copyright 2018, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmpopts + +import ( + "github.com/google/go-cmp/cmp" +) + +type xformFilter struct{ xform cmp.Option } + +func (xf xformFilter) filter(p cmp.Path) bool { + for _, ps := range p { + if t, ok := ps.(cmp.Transform); ok && t.Option() == xf.xform { + return false + } + } + return true +} + +// AcyclicTransformer returns a Transformer with a filter applied that ensures +// that the transformer cannot be recursively applied upon its own output. +// +// An example use case is a transformer that splits a string by lines: +// AcyclicTransformer("SplitLines", func(s string) []string{ +// return strings.Split(s, "\n") +// }) +// +// Had this been an unfiltered Transformer instead, this would result in an +// infinite cycle converting a string to []string to [][]string and so on. +func AcyclicTransformer(name string, xformFunc interface{}) cmp.Option { + xf := xformFilter{cmp.Transformer(name, xformFunc)} + return cmp.FilterPath(xf.filter, xf.xform) +} diff --git a/vendor/github.com/google/go-cmp/cmp/compare.go b/vendor/github.com/google/go-cmp/cmp/compare.go index 7e215f220296..2133562b01c3 100644 --- a/vendor/github.com/google/go-cmp/cmp/compare.go +++ b/vendor/github.com/google/go-cmp/cmp/compare.go @@ -29,26 +29,17 @@ package cmp import ( "fmt" "reflect" + "strings" "github.com/google/go-cmp/cmp/internal/diff" + "github.com/google/go-cmp/cmp/internal/flags" "github.com/google/go-cmp/cmp/internal/function" "github.com/google/go-cmp/cmp/internal/value" ) -// BUG(dsnet): Maps with keys containing NaN values cannot be properly compared due to -// the reflection package's inability to retrieve such entries. Equal will panic -// anytime it comes across a NaN key, but this behavior may change. -// -// See https://golang.org/issue/11104 for more details. - -var nothing = reflect.Value{} - // Equal reports whether x and y are equal by recursively applying the // following rules in the given order to x and y and all of their sub-values: // -// • If two values are not of the same type, then they are never equal -// and the overall result is false. -// // • Let S be the set of all Ignore, Transformer, and Comparer options that // remain after applying all path filters, value filters, and type filters. // If at least one Ignore exists in S, then the comparison is ignored. @@ -61,43 +52,79 @@ var nothing = reflect.Value{} // // • If the values have an Equal method of the form "(T) Equal(T) bool" or // "(T) Equal(I) bool" where T is assignable to I, then use the result of -// x.Equal(y) even if x or y is nil. -// Otherwise, no such method exists and evaluation proceeds to the next rule. +// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and +// evaluation proceeds to the next rule. // // • Lastly, try to compare x and y based on their basic kinds. // Simple kinds like booleans, integers, floats, complex numbers, strings, and // channels are compared using the equivalent of the == operator in Go. // Functions are only equal if they are both nil, otherwise they are unequal. -// Pointers are equal if the underlying values they point to are also equal. -// Interfaces are equal if their underlying concrete values are also equal. // -// Structs are equal if all of their fields are equal. If a struct contains -// unexported fields, Equal panics unless the AllowUnexported option is used or -// an Ignore option (e.g., cmpopts.IgnoreUnexported) ignores that field. +// Structs are equal if recursively calling Equal on all fields report equal. +// If a struct contains unexported fields, Equal panics unless an Ignore option +// (e.g., cmpopts.IgnoreUnexported) ignores that field or the AllowUnexported +// option explicitly permits comparing the unexported field. +// +// Slices are equal if they are both nil or both non-nil, where recursively +// calling Equal on all non-ignored slice or array elements report equal. +// Empty non-nil slices and nil slices are not equal; to equate empty slices, +// consider using cmpopts.EquateEmpty. // -// Arrays, slices, and maps are equal if they are both nil or both non-nil -// with the same length and the elements at each index or key are equal. -// Note that a non-nil empty slice and a nil slice are not equal. -// To equate empty slices and maps, consider using cmpopts.EquateEmpty. +// Maps are equal if they are both nil or both non-nil, where recursively +// calling Equal on all non-ignored map entries report equal. // Map keys are equal according to the == operator. // To use custom comparisons for map keys, consider using cmpopts.SortMaps. +// Empty non-nil maps and nil maps are not equal; to equate empty maps, +// consider using cmpopts.EquateEmpty. +// +// Pointers and interfaces are equal if they are both nil or both non-nil, +// where they have the same underlying concrete type and recursively +// calling Equal on the underlying values reports equal. func Equal(x, y interface{}, opts ...Option) bool { + vx := reflect.ValueOf(x) + vy := reflect.ValueOf(y) + + // If the inputs are different types, auto-wrap them in an empty interface + // so that they have the same parent type. + var t reflect.Type + if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() { + t = reflect.TypeOf((*interface{})(nil)).Elem() + if vx.IsValid() { + vvx := reflect.New(t).Elem() + vvx.Set(vx) + vx = vvx + } + if vy.IsValid() { + vvy := reflect.New(t).Elem() + vvy.Set(vy) + vy = vvy + } + } else { + t = vx.Type() + } + s := newState(opts) - s.compareAny(reflect.ValueOf(x), reflect.ValueOf(y)) + s.compareAny(&pathStep{t, vx, vy}) return s.result.Equal() } // Diff returns a human-readable report of the differences between two values. // It returns an empty string if and only if Equal returns true for the same -// input values and options. The output string will use the "-" symbol to -// indicate elements removed from x, and the "+" symbol to indicate elements -// added to y. +// input values and options. +// +// The output is displayed as a literal in pseudo-Go syntax. +// At the start of each line, a "-" prefix indicates an element removed from x, +// a "+" prefix to indicates an element added to y, and the lack of a prefix +// indicates an element common to both x and y. If possible, the output +// uses fmt.Stringer.String or error.Error methods to produce more humanly +// readable outputs. In such cases, the string is prefixed with either an +// 's' or 'e' character, respectively, to indicate that the method was called. // -// Do not depend on this output being stable. +// Do not depend on this output being stable. If you need the ability to +// programmatically interpret the difference, consider using a custom Reporter. func Diff(x, y interface{}, opts ...Option) string { r := new(defaultReporter) - opts = Options{Options(opts), r} - eq := Equal(x, y, opts...) + eq := Equal(x, y, Options(opts), Reporter(r)) d := r.String() if (d == "") != eq { panic("inconsistent difference and equality results") @@ -108,9 +135,13 @@ func Diff(x, y interface{}, opts ...Option) string { type state struct { // These fields represent the "comparison state". // Calling statelessCompare must not result in observable changes to these. - result diff.Result // The current result of comparison - curPath Path // The current path in the value tree - reporter reporter // Optional reporter used for difference formatting + result diff.Result // The current result of comparison + curPath Path // The current path in the value tree + reporters []reporter // Optional reporters + + // recChecker checks for infinite cycles applying the same set of + // transformers upon the output of itself. + recChecker recChecker // dynChecker triggers pseudo-random checks for option correctness. // It is safe for statelessCompare to mutate this value. @@ -122,10 +153,9 @@ type state struct { } func newState(opts []Option) *state { - s := new(state) - for _, opt := range opts { - s.processOption(opt) - } + // Always ensure a validator option exists to validate the inputs. + s := &state{opts: Options{validator{}}} + s.processOption(Options(opts)) return s } @@ -152,10 +182,7 @@ func (s *state) processOption(opt Option) { s.exporters[t] = true } case reporter: - if s.reporter != nil { - panic("difference reporter already registered") - } - s.reporter = opt + s.reporters = append(s.reporters, opt) default: panic(fmt.Sprintf("unknown option %T", opt)) } @@ -164,153 +191,88 @@ func (s *state) processOption(opt Option) { // statelessCompare compares two values and returns the result. // This function is stateless in that it does not alter the current result, // or output to any registered reporters. -func (s *state) statelessCompare(vx, vy reflect.Value) diff.Result { +func (s *state) statelessCompare(step PathStep) diff.Result { // We do not save and restore the curPath because all of the compareX // methods should properly push and pop from the path. // It is an implementation bug if the contents of curPath differs from // when calling this function to when returning from it. - oldResult, oldReporter := s.result, s.reporter + oldResult, oldReporters := s.result, s.reporters s.result = diff.Result{} // Reset result - s.reporter = nil // Remove reporter to avoid spurious printouts - s.compareAny(vx, vy) + s.reporters = nil // Remove reporters to avoid spurious printouts + s.compareAny(step) res := s.result - s.result, s.reporter = oldResult, oldReporter + s.result, s.reporters = oldResult, oldReporters return res } -func (s *state) compareAny(vx, vy reflect.Value) { - // TODO: Support cyclic data structures. - - // Rule 0: Differing types are never equal. - if !vx.IsValid() || !vy.IsValid() { - s.report(vx.IsValid() == vy.IsValid(), vx, vy) - return - } - if vx.Type() != vy.Type() { - s.report(false, vx, vy) // Possible for path to be empty - return - } - t := vx.Type() - if len(s.curPath) == 0 { - s.curPath.push(&pathStep{typ: t}) - defer s.curPath.pop() +func (s *state) compareAny(step PathStep) { + // Update the path stack. + s.curPath.push(step) + defer s.curPath.pop() + for _, r := range s.reporters { + r.PushStep(step) + defer r.PopStep() } - vx, vy = s.tryExporting(vx, vy) + s.recChecker.Check(s.curPath) + + // Obtain the current type and values. + t := step.Type() + vx, vy := step.Values() // Rule 1: Check whether an option applies on this node in the value tree. - if s.tryOptions(vx, vy, t) { + if s.tryOptions(t, vx, vy) { return } // Rule 2: Check whether the type has a valid Equal method. - if s.tryMethod(vx, vy, t) { + if s.tryMethod(t, vx, vy) { return } - // Rule 3: Recursively descend into each value's underlying kind. + // Rule 3: Compare based on the underlying kind. switch t.Kind() { case reflect.Bool: - s.report(vx.Bool() == vy.Bool(), vx, vy) - return + s.report(vx.Bool() == vy.Bool(), 0) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - s.report(vx.Int() == vy.Int(), vx, vy) - return + s.report(vx.Int() == vy.Int(), 0) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - s.report(vx.Uint() == vy.Uint(), vx, vy) - return + s.report(vx.Uint() == vy.Uint(), 0) case reflect.Float32, reflect.Float64: - s.report(vx.Float() == vy.Float(), vx, vy) - return + s.report(vx.Float() == vy.Float(), 0) case reflect.Complex64, reflect.Complex128: - s.report(vx.Complex() == vy.Complex(), vx, vy) - return + s.report(vx.Complex() == vy.Complex(), 0) case reflect.String: - s.report(vx.String() == vy.String(), vx, vy) - return + s.report(vx.String() == vy.String(), 0) case reflect.Chan, reflect.UnsafePointer: - s.report(vx.Pointer() == vy.Pointer(), vx, vy) - return + s.report(vx.Pointer() == vy.Pointer(), 0) case reflect.Func: - s.report(vx.IsNil() && vy.IsNil(), vx, vy) - return + s.report(vx.IsNil() && vy.IsNil(), 0) + case reflect.Struct: + s.compareStruct(t, vx, vy) + case reflect.Slice, reflect.Array: + s.compareSlice(t, vx, vy) + case reflect.Map: + s.compareMap(t, vx, vy) case reflect.Ptr: - if vx.IsNil() || vy.IsNil() { - s.report(vx.IsNil() && vy.IsNil(), vx, vy) - return - } - s.curPath.push(&indirect{pathStep{t.Elem()}}) - defer s.curPath.pop() - s.compareAny(vx.Elem(), vy.Elem()) - return + s.comparePtr(t, vx, vy) case reflect.Interface: - if vx.IsNil() || vy.IsNil() { - s.report(vx.IsNil() && vy.IsNil(), vx, vy) - return - } - if vx.Elem().Type() != vy.Elem().Type() { - s.report(false, vx.Elem(), vy.Elem()) - return - } - s.curPath.push(&typeAssertion{pathStep{vx.Elem().Type()}}) - defer s.curPath.pop() - s.compareAny(vx.Elem(), vy.Elem()) - return - case reflect.Slice: - if vx.IsNil() || vy.IsNil() { - s.report(vx.IsNil() && vy.IsNil(), vx, vy) - return - } - fallthrough - case reflect.Array: - s.compareArray(vx, vy, t) - return - case reflect.Map: - s.compareMap(vx, vy, t) - return - case reflect.Struct: - s.compareStruct(vx, vy, t) - return + s.compareInterface(t, vx, vy) default: panic(fmt.Sprintf("%v kind not handled", t.Kind())) } } -func (s *state) tryExporting(vx, vy reflect.Value) (reflect.Value, reflect.Value) { - if sf, ok := s.curPath[len(s.curPath)-1].(*structField); ok && sf.unexported { - if sf.force { - // Use unsafe pointer arithmetic to get read-write access to an - // unexported field in the struct. - vx = unsafeRetrieveField(sf.pvx, sf.field) - vy = unsafeRetrieveField(sf.pvy, sf.field) - } else { - // We are not allowed to export the value, so invalidate them - // so that tryOptions can panic later if not explicitly ignored. - vx = nothing - vy = nothing - } - } - return vx, vy -} - -func (s *state) tryOptions(vx, vy reflect.Value, t reflect.Type) bool { - // If there were no FilterValues, we will not detect invalid inputs, - // so manually check for them and append invalid if necessary. - // We still evaluate the options since an ignore can override invalid. - opts := s.opts - if !vx.IsValid() || !vy.IsValid() { - opts = Options{opts, invalid{}} - } - +func (s *state) tryOptions(t reflect.Type, vx, vy reflect.Value) bool { // Evaluate all filters and apply the remaining options. - if opt := opts.filter(s, vx, vy, t); opt != nil { + if opt := s.opts.filter(s, t, vx, vy); opt != nil { opt.apply(s, vx, vy) return true } return false } -func (s *state) tryMethod(vx, vy reflect.Value, t reflect.Type) bool { +func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool { // Check if this type even has an Equal method. m, ok := t.MethodByName("Equal") if !ok || !function.IsType(m.Type, function.EqualAssignable) { @@ -318,11 +280,11 @@ func (s *state) tryMethod(vx, vy reflect.Value, t reflect.Type) bool { } eq := s.callTTBFunc(m.Func, vx, vy) - s.report(eq, vx, vy) + s.report(eq, reportByMethod) return true } -func (s *state) callTRFunc(f, v reflect.Value) reflect.Value { +func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value { v = sanitizeValue(v, f.Type().In(0)) if !s.dynChecker.Next() { return f.Call([]reflect.Value{v})[0] @@ -333,15 +295,15 @@ func (s *state) callTRFunc(f, v reflect.Value) reflect.Value { // unsafe mutations to the input. c := make(chan reflect.Value) go detectRaces(c, f, v) + got := <-c want := f.Call([]reflect.Value{v})[0] - if got := <-c; !s.statelessCompare(got, want).Equal() { + if step.vx, step.vy = got, want; !s.statelessCompare(step).Equal() { // To avoid false-positives with non-reflexive equality operations, // we sanity check whether a value is equal to itself. - if !s.statelessCompare(want, want).Equal() { + if step.vx, step.vy = want, want; !s.statelessCompare(step).Equal() { return want } - fn := getFuncName(f.Pointer()) - panic(fmt.Sprintf("non-deterministic function detected: %s", fn)) + panic(fmt.Sprintf("non-deterministic function detected: %s", function.NameOf(f))) } return want } @@ -359,10 +321,10 @@ func (s *state) callTTBFunc(f, x, y reflect.Value) bool { // unsafe mutations to the input. c := make(chan reflect.Value) go detectRaces(c, f, y, x) + got := <-c want := f.Call([]reflect.Value{x, y})[0].Bool() - if got := <-c; !got.IsValid() || got.Bool() != want { - fn := getFuncName(f.Pointer()) - panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", fn)) + if !got.IsValid() || got.Bool() != want { + panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", function.NameOf(f))) } return want } @@ -380,140 +342,241 @@ func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) { // assuming that T is assignable to R. // Otherwise, it returns the input value as is. func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value { - // TODO(dsnet): Remove this hacky workaround. - // See https://golang.org/issue/22143 - if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t { - return reflect.New(t).Elem() + // TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/22143). + if !flags.AtLeastGo110 { + if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t { + return reflect.New(t).Elem() + } } return v } -func (s *state) compareArray(vx, vy reflect.Value, t reflect.Type) { - step := &sliceIndex{pathStep{t.Elem()}, 0, 0} - s.curPath.push(step) +func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) { + var vax, vay reflect.Value // Addressable versions of vx and vy - // Compute an edit-script for slices vx and vy. - es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result { - step.xkey, step.ykey = ix, iy - return s.statelessCompare(vx.Index(ix), vy.Index(iy)) - }) + step := StructField{&structField{}} + for i := 0; i < t.NumField(); i++ { + step.typ = t.Field(i).Type + step.vx = vx.Field(i) + step.vy = vy.Field(i) + step.name = t.Field(i).Name + step.idx = i + step.unexported = !isExported(step.name) + if step.unexported { + if step.name == "_" { + continue + } + // Defer checking of unexported fields until later to give an + // Ignore a chance to ignore the field. + if !vax.IsValid() || !vay.IsValid() { + // For retrieveUnexportedField to work, the parent struct must + // be addressable. Create a new copy of the values if + // necessary to make them addressable. + vax = makeAddressable(vx) + vay = makeAddressable(vy) + } + step.mayForce = s.exporters[t] + step.pvx = vax + step.pvy = vay + step.field = t.Field(i) + } + s.compareAny(step) + } +} - // Report the entire slice as is if the arrays are of primitive kind, - // and the arrays are different enough. - isPrimitive := false - switch t.Elem().Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, - reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: - isPrimitive = true - } - if isPrimitive && es.Dist() > (vx.Len()+vy.Len())/4 { - s.curPath.pop() // Pop first since we are reporting the whole slice - s.report(false, vx, vy) +func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) { + isSlice := t.Kind() == reflect.Slice + if isSlice && (vx.IsNil() || vy.IsNil()) { + s.report(vx.IsNil() && vy.IsNil(), 0) return } - // Replay the edit-script. + // TODO: Support cyclic data structures. + + step := SliceIndex{&sliceIndex{pathStep: pathStep{typ: t.Elem()}}} + withIndexes := func(ix, iy int) SliceIndex { + if ix >= 0 { + step.vx, step.xkey = vx.Index(ix), ix + } else { + step.vx, step.xkey = reflect.Value{}, -1 + } + if iy >= 0 { + step.vy, step.ykey = vy.Index(iy), iy + } else { + step.vy, step.ykey = reflect.Value{}, -1 + } + return step + } + + // Ignore options are able to ignore missing elements in a slice. + // However, detecting these reliably requires an optimal differencing + // algorithm, for which diff.Difference is not. + // + // Instead, we first iterate through both slices to detect which elements + // would be ignored if standing alone. The index of non-discarded elements + // are stored in a separate slice, which diffing is then performed on. + var indexesX, indexesY []int + var ignoredX, ignoredY []bool + for ix := 0; ix < vx.Len(); ix++ { + ignored := s.statelessCompare(withIndexes(ix, -1)).NumDiff == 0 + if !ignored { + indexesX = append(indexesX, ix) + } + ignoredX = append(ignoredX, ignored) + } + for iy := 0; iy < vy.Len(); iy++ { + ignored := s.statelessCompare(withIndexes(-1, iy)).NumDiff == 0 + if !ignored { + indexesY = append(indexesY, iy) + } + ignoredY = append(ignoredY, ignored) + } + + // Compute an edit-script for slices vx and vy (excluding ignored elements). + edits := diff.Difference(len(indexesX), len(indexesY), func(ix, iy int) diff.Result { + return s.statelessCompare(withIndexes(indexesX[ix], indexesY[iy])) + }) + + // Replay the ignore-scripts and the edit-script. var ix, iy int - for _, e := range es { + for ix < vx.Len() || iy < vy.Len() { + var e diff.EditType + switch { + case ix < len(ignoredX) && ignoredX[ix]: + e = diff.UniqueX + case iy < len(ignoredY) && ignoredY[iy]: + e = diff.UniqueY + default: + e, edits = edits[0], edits[1:] + } switch e { case diff.UniqueX: - step.xkey, step.ykey = ix, -1 - s.report(false, vx.Index(ix), nothing) + s.compareAny(withIndexes(ix, -1)) ix++ case diff.UniqueY: - step.xkey, step.ykey = -1, iy - s.report(false, nothing, vy.Index(iy)) + s.compareAny(withIndexes(-1, iy)) iy++ default: - step.xkey, step.ykey = ix, iy - if e == diff.Identity { - s.report(true, vx.Index(ix), vy.Index(iy)) - } else { - s.compareAny(vx.Index(ix), vy.Index(iy)) - } + s.compareAny(withIndexes(ix, iy)) ix++ iy++ } } - s.curPath.pop() - return } -func (s *state) compareMap(vx, vy reflect.Value, t reflect.Type) { +func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) { if vx.IsNil() || vy.IsNil() { - s.report(vx.IsNil() && vy.IsNil(), vx, vy) + s.report(vx.IsNil() && vy.IsNil(), 0) return } + // TODO: Support cyclic data structures. + // We combine and sort the two map keys so that we can perform the // comparisons in a deterministic order. - step := &mapIndex{pathStep: pathStep{t.Elem()}} - s.curPath.push(step) - defer s.curPath.pop() + step := MapIndex{&mapIndex{pathStep: pathStep{typ: t.Elem()}}} for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) { + step.vx = vx.MapIndex(k) + step.vy = vy.MapIndex(k) step.key = k - vvx := vx.MapIndex(k) - vvy := vy.MapIndex(k) - switch { - case vvx.IsValid() && vvy.IsValid(): - s.compareAny(vvx, vvy) - case vvx.IsValid() && !vvy.IsValid(): - s.report(false, vvx, nothing) - case !vvx.IsValid() && vvy.IsValid(): - s.report(false, nothing, vvy) - default: - // It is possible for both vvx and vvy to be invalid if the - // key contained a NaN value in it. There is no way in - // reflection to be able to retrieve these values. - // See https://golang.org/issue/11104 - panic(fmt.Sprintf("%#v has map key with NaNs", s.curPath)) + if !step.vx.IsValid() && !step.vy.IsValid() { + // It is possible for both vx and vy to be invalid if the + // key contained a NaN value in it. + // + // Even with the ability to retrieve NaN keys in Go 1.12, + // there still isn't a sensible way to compare the values since + // a NaN key may map to multiple unordered values. + // The most reasonable way to compare NaNs would be to compare the + // set of values. However, this is impossible to do efficiently + // since set equality is provably an O(n^2) operation given only + // an Equal function. If we had a Less function or Hash function, + // this could be done in O(n*log(n)) or O(n), respectively. + // + // Rather than adding complex logic to deal with NaNs, make it + // the user's responsibility to compare such obscure maps. + const help = "consider providing a Comparer to compare the map" + panic(fmt.Sprintf("%#v has map key with NaNs\n%s", s.curPath, help)) } + s.compareAny(step) } } -func (s *state) compareStruct(vx, vy reflect.Value, t reflect.Type) { - var vax, vay reflect.Value // Addressable versions of vx and vy +func (s *state) comparePtr(t reflect.Type, vx, vy reflect.Value) { + if vx.IsNil() || vy.IsNil() { + s.report(vx.IsNil() && vy.IsNil(), 0) + return + } - step := &structField{} - s.curPath.push(step) - defer s.curPath.pop() - for i := 0; i < t.NumField(); i++ { - vvx := vx.Field(i) - vvy := vy.Field(i) - step.typ = t.Field(i).Type - step.name = t.Field(i).Name - step.idx = i - step.unexported = !isExported(step.name) - if step.unexported { - // Defer checking of unexported fields until later to give an - // Ignore a chance to ignore the field. - if !vax.IsValid() || !vay.IsValid() { - // For unsafeRetrieveField to work, the parent struct must - // be addressable. Create a new copy of the values if - // necessary to make them addressable. - vax = makeAddressable(vx) - vay = makeAddressable(vy) - } - step.force = s.exporters[t] - step.pvx = vax - step.pvy = vay - step.field = t.Field(i) + // TODO: Support cyclic data structures. + + vx, vy = vx.Elem(), vy.Elem() + s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}}) +} + +func (s *state) compareInterface(t reflect.Type, vx, vy reflect.Value) { + if vx.IsNil() || vy.IsNil() { + s.report(vx.IsNil() && vy.IsNil(), 0) + return + } + vx, vy = vx.Elem(), vy.Elem() + if vx.Type() != vy.Type() { + s.report(false, 0) + return + } + s.compareAny(TypeAssertion{&typeAssertion{pathStep{vx.Type(), vx, vy}}}) +} + +func (s *state) report(eq bool, rf resultFlags) { + if rf&reportByIgnore == 0 { + if eq { + s.result.NumSame++ + rf |= reportEqual + } else { + s.result.NumDiff++ + rf |= reportUnequal } - s.compareAny(vvx, vvy) + } + for _, r := range s.reporters { + r.Report(Result{flags: rf}) } } -// report records the result of a single comparison. -// It also calls Report if any reporter is registered. -func (s *state) report(eq bool, vx, vy reflect.Value) { - if eq { - s.result.NSame++ - } else { - s.result.NDiff++ +// recChecker tracks the state needed to periodically perform checks that +// user provided transformers are not stuck in an infinitely recursive cycle. +type recChecker struct{ next int } + +// Check scans the Path for any recursive transformers and panics when any +// recursive transformers are detected. Note that the presence of a +// recursive Transformer does not necessarily imply an infinite cycle. +// As such, this check only activates after some minimal number of path steps. +func (rc *recChecker) Check(p Path) { + const minLen = 1 << 16 + if rc.next == 0 { + rc.next = minLen + } + if len(p) < rc.next { + return + } + rc.next <<= 1 + + // Check whether the same transformer has appeared at least twice. + var ss []string + m := map[Option]int{} + for _, ps := range p { + if t, ok := ps.(Transform); ok { + t := t.Option() + if m[t] == 1 { // Transformer was used exactly once before + tf := t.(*transformer).fnc.Type() + ss = append(ss, fmt.Sprintf("%v: %v => %v", t, tf.In(0), tf.Out(0))) + } + m[t]++ + } } - if s.reporter != nil { - s.reporter.Report(vx, vy, eq, s.curPath) + if len(ss) > 0 { + const warning = "recursive set of Transformers detected" + const help = "consider using cmpopts.AcyclicTransformer" + set := strings.Join(ss, "\n\t") + panic(fmt.Sprintf("%s:\n\t%s\n%s", warning, set, help)) } } diff --git a/vendor/github.com/google/go-cmp/cmp/unsafe_panic.go b/vendor/github.com/google/go-cmp/cmp/export_panic.go similarity index 60% rename from vendor/github.com/google/go-cmp/cmp/unsafe_panic.go rename to vendor/github.com/google/go-cmp/cmp/export_panic.go index d1518eb3a8c7..abc3a1c3e765 100644 --- a/vendor/github.com/google/go-cmp/cmp/unsafe_panic.go +++ b/vendor/github.com/google/go-cmp/cmp/export_panic.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE.md file. -// +build purego appengine js +// +build purego package cmp @@ -10,6 +10,6 @@ import "reflect" const supportAllowUnexported = false -func unsafeRetrieveField(reflect.Value, reflect.StructField) reflect.Value { - panic("unsafeRetrieveField is not implemented") +func retrieveUnexportedField(reflect.Value, reflect.StructField) reflect.Value { + panic("retrieveUnexportedField is not implemented") } diff --git a/vendor/github.com/google/go-cmp/cmp/unsafe_reflect.go b/vendor/github.com/google/go-cmp/cmp/export_unsafe.go similarity index 64% rename from vendor/github.com/google/go-cmp/cmp/unsafe_reflect.go rename to vendor/github.com/google/go-cmp/cmp/export_unsafe.go index 579b65507f6b..59d4ee91b47f 100644 --- a/vendor/github.com/google/go-cmp/cmp/unsafe_reflect.go +++ b/vendor/github.com/google/go-cmp/cmp/export_unsafe.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE.md file. -// +build !purego,!appengine,!js +// +build !purego package cmp @@ -13,11 +13,11 @@ import ( const supportAllowUnexported = true -// unsafeRetrieveField uses unsafe to forcibly retrieve any field from a struct -// such that the value has read-write permissions. +// retrieveUnexportedField uses unsafe to forcibly retrieve any field from +// a struct such that the value has read-write permissions. // // The parent struct, v, must be addressable, while f must be a StructField // describing the field to retrieve. -func unsafeRetrieveField(v reflect.Value, f reflect.StructField) reflect.Value { +func retrieveUnexportedField(v reflect.Value, f reflect.StructField) reflect.Value { return reflect.NewAt(f.Type, unsafe.Pointer(v.UnsafeAddr()+f.Offset)).Elem() } diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go index 42afa4960efa..fe98dcc67746 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE.md file. -// +build !debug +// +build !cmp_debug package diff diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go index fd9f7f177399..597b6ae56b1b 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE.md file. -// +build debug +// +build cmp_debug package diff @@ -14,7 +14,7 @@ import ( ) // The algorithm can be seen running in real-time by enabling debugging: -// go test -tags=debug -v +// go test -tags=cmp_debug -v // // Example output: // === RUN TestDifference/#34 diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go index 260befea2fd7..3d2e42662ca3 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go @@ -85,22 +85,31 @@ func (es EditScript) LenY() int { return len(es) - es.stats().NX } type EqualFunc func(ix int, iy int) Result // Result is the result of comparison. -// NSame is the number of sub-elements that are equal. -// NDiff is the number of sub-elements that are not equal. -type Result struct{ NSame, NDiff int } +// NumSame is the number of sub-elements that are equal. +// NumDiff is the number of sub-elements that are not equal. +type Result struct{ NumSame, NumDiff int } + +// BoolResult returns a Result that is either Equal or not Equal. +func BoolResult(b bool) Result { + if b { + return Result{NumSame: 1} // Equal, Similar + } else { + return Result{NumDiff: 2} // Not Equal, not Similar + } +} // Equal indicates whether the symbols are equal. Two symbols are equal -// if and only if NDiff == 0. If Equal, then they are also Similar. -func (r Result) Equal() bool { return r.NDiff == 0 } +// if and only if NumDiff == 0. If Equal, then they are also Similar. +func (r Result) Equal() bool { return r.NumDiff == 0 } // Similar indicates whether two symbols are similar and may be represented // by using the Modified type. As a special case, we consider binary comparisons // (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar. // -// The exact ratio of NSame to NDiff to determine similarity may change. +// The exact ratio of NumSame to NumDiff to determine similarity may change. func (r Result) Similar() bool { - // Use NSame+1 to offset NSame so that binary comparisons are similar. - return r.NSame+1 >= r.NDiff + // Use NumSame+1 to offset NumSame so that binary comparisons are similar. + return r.NumSame+1 >= r.NumDiff } // Difference reports whether two lists of lengths nx and ny are equal @@ -191,9 +200,9 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) { // that two lists commonly differ because elements were added to the front // or end of the other list. // - // Running the tests with the "debug" build tag prints a visualization of - // the algorithm running in real-time. This is educational for understanding - // how the algorithm works. See debug_enable.go. + // Running the tests with the "cmp_debug" build tag prints a visualization + // of the algorithm running in real-time. This is educational for + // understanding how the algorithm works. See debug_enable.go. f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es) for { // Forward search from the beginning. diff --git a/vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go b/vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go new file mode 100644 index 000000000000..a9e7fc0b5b39 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go @@ -0,0 +1,9 @@ +// Copyright 2019, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package flags + +// Deterministic controls whether the output of Diff should be deterministic. +// This is only used for testing. +var Deterministic bool diff --git a/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go b/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go new file mode 100644 index 000000000000..01aed0a1532b --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go @@ -0,0 +1,10 @@ +// Copyright 2019, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +// +build !go1.10 + +package flags + +// AtLeastGo110 reports whether the Go toolchain is at least Go 1.10. +const AtLeastGo110 = false diff --git a/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go b/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go new file mode 100644 index 000000000000..c0b667f58b05 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go @@ -0,0 +1,10 @@ +// Copyright 2019, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +// +build go1.10 + +package flags + +// AtLeastGo110 reports whether the Go toolchain is at least Go 1.10. +const AtLeastGo110 = true diff --git a/vendor/github.com/google/go-cmp/cmp/internal/function/func.go b/vendor/github.com/google/go-cmp/cmp/internal/function/func.go index 4c35ff11ee13..ace1dbe86e57 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/function/func.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/function/func.go @@ -2,25 +2,34 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE.md file. -// Package function identifies function types. +// Package function provides functionality for identifying function types. package function -import "reflect" +import ( + "reflect" + "regexp" + "runtime" + "strings" +) type funcType int const ( _ funcType = iota + tbFunc // func(T) bool ttbFunc // func(T, T) bool + trbFunc // func(T, R) bool tibFunc // func(T, I) bool trFunc // func(T) R - Equal = ttbFunc // func(T, T) bool - EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool - Transformer = trFunc // func(T) R - ValueFilter = ttbFunc // func(T, T) bool - Less = ttbFunc // func(T, T) bool + Equal = ttbFunc // func(T, T) bool + EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool + Transformer = trFunc // func(T) R + ValueFilter = ttbFunc // func(T, T) bool + Less = ttbFunc // func(T, T) bool + ValuePredicate = tbFunc // func(T) bool + KeyValuePredicate = trbFunc // func(T, R) bool ) var boolType = reflect.TypeOf(true) @@ -32,10 +41,18 @@ func IsType(t reflect.Type, ft funcType) bool { } ni, no := t.NumIn(), t.NumOut() switch ft { + case tbFunc: // func(T) bool + if ni == 1 && no == 1 && t.Out(0) == boolType { + return true + } case ttbFunc: // func(T, T) bool if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType { return true } + case trbFunc: // func(T, R) bool + if ni == 2 && no == 1 && t.Out(0) == boolType { + return true + } case tibFunc: // func(T, I) bool if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType { return true @@ -47,3 +64,36 @@ func IsType(t reflect.Type, ft funcType) bool { } return false } + +var lastIdentRx = regexp.MustCompile(`[_\p{L}][_\p{L}\p{N}]*$`) + +// NameOf returns the name of the function value. +func NameOf(v reflect.Value) string { + fnc := runtime.FuncForPC(v.Pointer()) + if fnc == nil { + return "" + } + fullName := fnc.Name() // e.g., "long/path/name/mypkg.(*MyType).(long/path/name/mypkg.myMethod)-fm" + + // Method closures have a "-fm" suffix. + fullName = strings.TrimSuffix(fullName, "-fm") + + var name string + for len(fullName) > 0 { + inParen := strings.HasSuffix(fullName, ")") + fullName = strings.TrimSuffix(fullName, ")") + + s := lastIdentRx.FindString(fullName) + if s == "" { + break + } + name = s + "." + name + fullName = strings.TrimSuffix(fullName, s) + + if i := strings.LastIndexByte(fullName, '('); inParen && i >= 0 { + fullName = fullName[:i] + } + fullName = strings.TrimSuffix(fullName, ".") + } + return strings.TrimSuffix(name, ".") +} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/format.go b/vendor/github.com/google/go-cmp/cmp/internal/value/format.go deleted file mode 100644 index 657e508779db..000000000000 --- a/vendor/github.com/google/go-cmp/cmp/internal/value/format.go +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE.md file. - -// Package value provides functionality for reflect.Value types. -package value - -import ( - "fmt" - "reflect" - "strconv" - "strings" - "unicode" -) - -var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() - -// Format formats the value v as a string. -// -// This is similar to fmt.Sprintf("%+v", v) except this: -// * Prints the type unless it can be elided -// * Avoids printing struct fields that are zero -// * Prints a nil-slice as being nil, not empty -// * Prints map entries in deterministic order -func Format(v reflect.Value, conf FormatConfig) string { - conf.printType = true - conf.followPointers = true - conf.realPointers = true - return formatAny(v, conf, nil) -} - -type FormatConfig struct { - UseStringer bool // Should the String method be used if available? - printType bool // Should we print the type before the value? - PrintPrimitiveType bool // Should we print the type of primitives? - followPointers bool // Should we recursively follow pointers? - realPointers bool // Should we print the real address of pointers? -} - -func formatAny(v reflect.Value, conf FormatConfig, visited map[uintptr]bool) string { - // TODO: Should this be a multi-line printout in certain situations? - - if !v.IsValid() { - return "" - } - if conf.UseStringer && v.Type().Implements(stringerIface) && v.CanInterface() { - if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && v.IsNil() { - return "" - } - - const stringerPrefix = "s" // Indicates that the String method was used - s := v.Interface().(fmt.Stringer).String() - return stringerPrefix + formatString(s) - } - - switch v.Kind() { - case reflect.Bool: - return formatPrimitive(v.Type(), v.Bool(), conf) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return formatPrimitive(v.Type(), v.Int(), conf) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr { - // Unnamed uints are usually bytes or words, so use hexadecimal. - return formatPrimitive(v.Type(), formatHex(v.Uint()), conf) - } - return formatPrimitive(v.Type(), v.Uint(), conf) - case reflect.Float32, reflect.Float64: - return formatPrimitive(v.Type(), v.Float(), conf) - case reflect.Complex64, reflect.Complex128: - return formatPrimitive(v.Type(), v.Complex(), conf) - case reflect.String: - return formatPrimitive(v.Type(), formatString(v.String()), conf) - case reflect.UnsafePointer, reflect.Chan, reflect.Func: - return formatPointer(v, conf) - case reflect.Ptr: - if v.IsNil() { - if conf.printType { - return fmt.Sprintf("(%v)(nil)", v.Type()) - } - return "" - } - if visited[v.Pointer()] || !conf.followPointers { - return formatPointer(v, conf) - } - visited = insertPointer(visited, v.Pointer()) - return "&" + formatAny(v.Elem(), conf, visited) - case reflect.Interface: - if v.IsNil() { - if conf.printType { - return fmt.Sprintf("%v(nil)", v.Type()) - } - return "" - } - return formatAny(v.Elem(), conf, visited) - case reflect.Slice: - if v.IsNil() { - if conf.printType { - return fmt.Sprintf("%v(nil)", v.Type()) - } - return "" - } - if visited[v.Pointer()] { - return formatPointer(v, conf) - } - visited = insertPointer(visited, v.Pointer()) - fallthrough - case reflect.Array: - var ss []string - subConf := conf - subConf.printType = v.Type().Elem().Kind() == reflect.Interface - for i := 0; i < v.Len(); i++ { - s := formatAny(v.Index(i), subConf, visited) - ss = append(ss, s) - } - s := fmt.Sprintf("{%s}", strings.Join(ss, ", ")) - if conf.printType { - return v.Type().String() + s - } - return s - case reflect.Map: - if v.IsNil() { - if conf.printType { - return fmt.Sprintf("%v(nil)", v.Type()) - } - return "" - } - if visited[v.Pointer()] { - return formatPointer(v, conf) - } - visited = insertPointer(visited, v.Pointer()) - - var ss []string - keyConf, valConf := conf, conf - keyConf.printType = v.Type().Key().Kind() == reflect.Interface - keyConf.followPointers = false - valConf.printType = v.Type().Elem().Kind() == reflect.Interface - for _, k := range SortKeys(v.MapKeys()) { - sk := formatAny(k, keyConf, visited) - sv := formatAny(v.MapIndex(k), valConf, visited) - ss = append(ss, fmt.Sprintf("%s: %s", sk, sv)) - } - s := fmt.Sprintf("{%s}", strings.Join(ss, ", ")) - if conf.printType { - return v.Type().String() + s - } - return s - case reflect.Struct: - var ss []string - subConf := conf - subConf.printType = true - for i := 0; i < v.NumField(); i++ { - vv := v.Field(i) - if isZero(vv) { - continue // Elide zero value fields - } - name := v.Type().Field(i).Name - subConf.UseStringer = conf.UseStringer - s := formatAny(vv, subConf, visited) - ss = append(ss, fmt.Sprintf("%s: %s", name, s)) - } - s := fmt.Sprintf("{%s}", strings.Join(ss, ", ")) - if conf.printType { - return v.Type().String() + s - } - return s - default: - panic(fmt.Sprintf("%v kind not handled", v.Kind())) - } -} - -func formatString(s string) string { - // Use quoted string if it the same length as a raw string literal. - // Otherwise, attempt to use the raw string form. - qs := strconv.Quote(s) - if len(qs) == 1+len(s)+1 { - return qs - } - - // Disallow newlines to ensure output is a single line. - // Only allow printable runes for readability purposes. - rawInvalid := func(r rune) bool { - return r == '`' || r == '\n' || !unicode.IsPrint(r) - } - if strings.IndexFunc(s, rawInvalid) < 0 { - return "`" + s + "`" - } - return qs -} - -func formatPrimitive(t reflect.Type, v interface{}, conf FormatConfig) string { - if conf.printType && (conf.PrintPrimitiveType || t.PkgPath() != "") { - return fmt.Sprintf("%v(%v)", t, v) - } - return fmt.Sprintf("%v", v) -} - -func formatPointer(v reflect.Value, conf FormatConfig) string { - p := v.Pointer() - if !conf.realPointers { - p = 0 // For deterministic printing purposes - } - s := formatHex(uint64(p)) - if conf.printType { - return fmt.Sprintf("(%v)(%s)", v.Type(), s) - } - return s -} - -func formatHex(u uint64) string { - var f string - switch { - case u <= 0xff: - f = "0x%02x" - case u <= 0xffff: - f = "0x%04x" - case u <= 0xffffff: - f = "0x%06x" - case u <= 0xffffffff: - f = "0x%08x" - case u <= 0xffffffffff: - f = "0x%010x" - case u <= 0xffffffffffff: - f = "0x%012x" - case u <= 0xffffffffffffff: - f = "0x%014x" - case u <= 0xffffffffffffffff: - f = "0x%016x" - } - return fmt.Sprintf(f, u) -} - -// insertPointer insert p into m, allocating m if necessary. -func insertPointer(m map[uintptr]bool, p uintptr) map[uintptr]bool { - if m == nil { - m = make(map[uintptr]bool) - } - m[p] = true - return m -} - -// isZero reports whether v is the zero value. -// This does not rely on Interface and so can be used on unexported fields. -func isZero(v reflect.Value) bool { - switch v.Kind() { - case reflect.Bool: - return v.Bool() == false - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Complex64, reflect.Complex128: - return v.Complex() == 0 - case reflect.String: - return v.String() == "" - case reflect.UnsafePointer: - return v.Pointer() == 0 - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: - return v.IsNil() - case reflect.Array: - for i := 0; i < v.Len(); i++ { - if !isZero(v.Index(i)) { - return false - } - } - return true - case reflect.Struct: - for i := 0; i < v.NumField(); i++ { - if !isZero(v.Field(i)) { - return false - } - } - return true - } - return false -} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go b/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go new file mode 100644 index 000000000000..0a01c4796f14 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go @@ -0,0 +1,23 @@ +// Copyright 2018, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +// +build purego + +package value + +import "reflect" + +// Pointer is an opaque typed pointer and is guaranteed to be comparable. +type Pointer struct { + p uintptr + t reflect.Type +} + +// PointerOf returns a Pointer from v, which must be a +// reflect.Ptr, reflect.Slice, or reflect.Map. +func PointerOf(v reflect.Value) Pointer { + // NOTE: Storing a pointer as an uintptr is technically incorrect as it + // assumes that the GC implementation does not use a moving collector. + return Pointer{v.Pointer(), v.Type()} +} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go b/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go new file mode 100644 index 000000000000..da134ae2a80f --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go @@ -0,0 +1,26 @@ +// Copyright 2018, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +// +build !purego + +package value + +import ( + "reflect" + "unsafe" +) + +// Pointer is an opaque typed pointer and is guaranteed to be comparable. +type Pointer struct { + p unsafe.Pointer + t reflect.Type +} + +// PointerOf returns a Pointer from v, which must be a +// reflect.Ptr, reflect.Slice, or reflect.Map. +func PointerOf(v reflect.Value) Pointer { + // The proper representation of a pointer is unsafe.Pointer, + // which is necessary if the GC ever uses a moving collector. + return Pointer{unsafe.Pointer(v.Pointer()), v.Type()} +} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go b/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go index fe8aa27a077b..938f646f000a 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go @@ -19,7 +19,7 @@ func SortKeys(vs []reflect.Value) []reflect.Value { } // Sort the map keys. - sort.Sort(valueSorter(vs)) + sort.Slice(vs, func(i, j int) bool { return isLess(vs[i], vs[j]) }) // Deduplicate keys (fails for NaNs). vs2 := vs[:1] @@ -31,13 +31,6 @@ func SortKeys(vs []reflect.Value) []reflect.Value { return vs2 } -// TODO: Use sort.Slice once Google AppEngine is on Go1.8 or above. -type valueSorter []reflect.Value - -func (vs valueSorter) Len() int { return len(vs) } -func (vs valueSorter) Less(i, j int) bool { return isLess(vs[i], vs[j]) } -func (vs valueSorter) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } - // isLess is a generic function for sorting arbitrary map keys. // The inputs must be of the same type and must be comparable. func isLess(x, y reflect.Value) bool { diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go b/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go new file mode 100644 index 000000000000..d13a12ccfcd6 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go @@ -0,0 +1,45 @@ +// Copyright 2017, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package value + +import "reflect" + +// IsZero reports whether v is the zero value. +// This does not rely on Interface and so can be used on unexported fields. +func IsZero(v reflect.Value) bool { + switch v.Kind() { + case reflect.Bool: + return v.Bool() == false + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Complex64, reflect.Complex128: + return v.Complex() == 0 + case reflect.String: + return v.String() == "" + case reflect.UnsafePointer: + return v.Pointer() == 0 + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: + return v.IsNil() + case reflect.Array: + for i := 0; i < v.Len(); i++ { + if !IsZero(v.Index(i)) { + return false + } + } + return true + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + if !IsZero(v.Field(i)) { + return false + } + } + return true + } + return false +} diff --git a/vendor/github.com/google/go-cmp/cmp/options.go b/vendor/github.com/google/go-cmp/cmp/options.go index 91d4b066e052..793448160ee7 100644 --- a/vendor/github.com/google/go-cmp/cmp/options.go +++ b/vendor/github.com/google/go-cmp/cmp/options.go @@ -7,7 +7,7 @@ package cmp import ( "fmt" "reflect" - "runtime" + "regexp" "strings" "github.com/google/go-cmp/cmp/internal/function" @@ -29,11 +29,11 @@ type Option interface { // An Options is returned only if multiple comparers or transformers // can apply simultaneously and will only contain values of those types // or sub-Options containing values of those types. - filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption + filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption } // applicableOption represents the following types: -// Fundamental: ignore | invalid | *comparer | *transformer +// Fundamental: ignore | validator | *comparer | *transformer // Grouping: Options type applicableOption interface { Option @@ -43,7 +43,7 @@ type applicableOption interface { } // coreOption represents the following types: -// Fundamental: ignore | invalid | *comparer | *transformer +// Fundamental: ignore | validator | *comparer | *transformer // Filters: *pathFilter | *valuesFilter type coreOption interface { Option @@ -63,19 +63,19 @@ func (core) isCore() {} // on all individual options held within. type Options []Option -func (opts Options) filter(s *state, vx, vy reflect.Value, t reflect.Type) (out applicableOption) { +func (opts Options) filter(s *state, t reflect.Type, vx, vy reflect.Value) (out applicableOption) { for _, opt := range opts { - switch opt := opt.filter(s, vx, vy, t); opt.(type) { + switch opt := opt.filter(s, t, vx, vy); opt.(type) { case ignore: return ignore{} // Only ignore can short-circuit evaluation - case invalid: - out = invalid{} // Takes precedence over comparer or transformer + case validator: + out = validator{} // Takes precedence over comparer or transformer case *comparer, *transformer, Options: switch out.(type) { case nil: out = opt - case invalid: - // Keep invalid + case validator: + // Keep validator case *comparer, *transformer, Options: out = Options{out, opt} // Conflicting comparers or transformers } @@ -106,6 +106,11 @@ func (opts Options) String() string { // FilterPath returns a new Option where opt is only evaluated if filter f // returns true for the current Path in the value tree. // +// This filter is called even if a slice element or map entry is missing and +// provides an opportunity to ignore such cases. The filter function must be +// symmetric such that the filter result is identical regardless of whether the +// missing value is from x or y. +// // The option passed in may be an Ignore, Transformer, Comparer, Options, or // a previously filtered Option. func FilterPath(f func(Path) bool, opt Option) Option { @@ -124,22 +129,22 @@ type pathFilter struct { opt Option } -func (f pathFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption { +func (f pathFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption { if f.fnc(s.curPath) { - return f.opt.filter(s, vx, vy, t) + return f.opt.filter(s, t, vx, vy) } return nil } func (f pathFilter) String() string { - fn := getFuncName(reflect.ValueOf(f.fnc).Pointer()) - return fmt.Sprintf("FilterPath(%s, %v)", fn, f.opt) + return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt) } // FilterValues returns a new Option where opt is only evaluated if filter f, // which is a function of the form "func(T, T) bool", returns true for the -// current pair of values being compared. If the type of the values is not -// assignable to T, then this filter implicitly returns false. +// current pair of values being compared. If either value is invalid or +// the type of the values is not assignable to T, then this filter implicitly +// returns false. // // The filter function must be // symmetric (i.e., agnostic to the order of the inputs) and @@ -171,19 +176,18 @@ type valuesFilter struct { opt Option } -func (f valuesFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) applicableOption { - if !vx.IsValid() || !vy.IsValid() { - return invalid{} +func (f valuesFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption { + if !vx.IsValid() || !vx.CanInterface() || !vy.IsValid() || !vy.CanInterface() { + return nil } if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) { - return f.opt.filter(s, vx, vy, t) + return f.opt.filter(s, t, vx, vy) } return nil } func (f valuesFilter) String() string { - fn := getFuncName(f.fnc.Pointer()) - return fmt.Sprintf("FilterValues(%s, %v)", fn, f.opt) + return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt) } // Ignore is an Option that causes all comparisons to be ignored. @@ -194,20 +198,45 @@ func Ignore() Option { return ignore{} } type ignore struct{ core } func (ignore) isFiltered() bool { return false } -func (ignore) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return ignore{} } -func (ignore) apply(_ *state, _, _ reflect.Value) { return } +func (ignore) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { return ignore{} } +func (ignore) apply(s *state, _, _ reflect.Value) { s.report(true, reportByIgnore) } func (ignore) String() string { return "Ignore()" } -// invalid is a sentinel Option type to indicate that some options could not -// be evaluated due to unexported fields. -type invalid struct{ core } +// validator is a sentinel Option type to indicate that some options could not +// be evaluated due to unexported fields, missing slice elements, or +// missing map entries. Both values are validator only for unexported fields. +type validator struct{ core } + +func (validator) filter(_ *state, _ reflect.Type, vx, vy reflect.Value) applicableOption { + if !vx.IsValid() || !vy.IsValid() { + return validator{} + } + if !vx.CanInterface() || !vy.CanInterface() { + return validator{} + } + return nil +} +func (validator) apply(s *state, vx, vy reflect.Value) { + // Implies missing slice element or map entry. + if !vx.IsValid() || !vy.IsValid() { + s.report(vx.IsValid() == vy.IsValid(), 0) + return + } + + // Unable to Interface implies unexported field without visibility access. + if !vx.CanInterface() || !vy.CanInterface() { + const help = "consider using a custom Comparer; if you control the implementation of type, you can also consider AllowUnexported or cmpopts.IgnoreUnexported" + panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help)) + } -func (invalid) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return invalid{} } -func (invalid) apply(s *state, _, _ reflect.Value) { - const help = "consider using AllowUnexported or cmpopts.IgnoreUnexported" - panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help)) + panic("not reachable") } +// identRx represents a valid identifier according to the Go specification. +const identRx = `[_\p{L}][_\p{L}\p{N}]*` + +var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`) + // Transformer returns an Option that applies a transformation function that // converts values of a certain type into that of another. // @@ -220,18 +249,25 @@ func (invalid) apply(s *state, _, _ reflect.Value) { // input and output types are the same), an implicit filter is added such that // a transformer is applicable only if that exact transformer is not already // in the tail of the Path since the last non-Transform step. +// For situations where the implicit filter is still insufficient, +// consider using cmpopts.AcyclicTransformer, which adds a filter +// to prevent the transformer from being recursively applied upon itself. // // The name is a user provided label that is used as the Transform.Name in the -// transformation PathStep. If empty, an arbitrary name is used. +// transformation PathStep (and eventually shown in the Diff output). +// The name must be a valid identifier or qualified identifier in Go syntax. +// If empty, an arbitrary name is used. func Transformer(name string, f interface{}) Option { v := reflect.ValueOf(f) if !function.IsType(v.Type(), function.Transformer) || v.IsNil() { panic(fmt.Sprintf("invalid transformer function: %T", f)) } if name == "" { - name = "λ" // Lambda-symbol as place-holder for anonymous transformer - } - if !isValid(name) { + name = function.NameOf(v) + if !identsRx.MatchString(name) { + name = "λ" // Lambda-symbol as placeholder name + } + } else if !identsRx.MatchString(name) { panic(fmt.Sprintf("invalid name: %q", name)) } tr := &transformer{name: name, fnc: reflect.ValueOf(f)} @@ -250,9 +286,9 @@ type transformer struct { func (tr *transformer) isFiltered() bool { return tr.typ != nil } -func (tr *transformer) filter(s *state, _, _ reflect.Value, t reflect.Type) applicableOption { +func (tr *transformer) filter(s *state, t reflect.Type, _, _ reflect.Value) applicableOption { for i := len(s.curPath) - 1; i >= 0; i-- { - if t, ok := s.curPath[i].(*transform); !ok { + if t, ok := s.curPath[i].(Transform); !ok { break // Hit most recent non-Transform step } else if tr == t.trans { return nil // Cannot directly use same Transform @@ -265,18 +301,15 @@ func (tr *transformer) filter(s *state, _, _ reflect.Value, t reflect.Type) appl } func (tr *transformer) apply(s *state, vx, vy reflect.Value) { - // Update path before calling the Transformer so that dynamic checks - // will use the updated path. - s.curPath.push(&transform{pathStep{tr.fnc.Type().Out(0)}, tr}) - defer s.curPath.pop() - - vx = s.callTRFunc(tr.fnc, vx) - vy = s.callTRFunc(tr.fnc, vy) - s.compareAny(vx, vy) + step := Transform{&transform{pathStep{typ: tr.fnc.Type().Out(0)}, tr}} + vvx := s.callTRFunc(tr.fnc, vx, step) + vvy := s.callTRFunc(tr.fnc, vy, step) + step.vx, step.vy = vvx, vvy + s.compareAny(step) } func (tr transformer) String() string { - return fmt.Sprintf("Transformer(%s, %s)", tr.name, getFuncName(tr.fnc.Pointer())) + return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc)) } // Comparer returns an Option that determines whether two values are equal @@ -311,7 +344,7 @@ type comparer struct { func (cm *comparer) isFiltered() bool { return cm.typ != nil } -func (cm *comparer) filter(_ *state, _, _ reflect.Value, t reflect.Type) applicableOption { +func (cm *comparer) filter(_ *state, t reflect.Type, _, _ reflect.Value) applicableOption { if cm.typ == nil || t.AssignableTo(cm.typ) { return cm } @@ -320,11 +353,11 @@ func (cm *comparer) filter(_ *state, _, _ reflect.Value, t reflect.Type) applica func (cm *comparer) apply(s *state, vx, vy reflect.Value) { eq := s.callTTBFunc(cm.fnc, vx, vy) - s.report(eq, vx, vy) + s.report(eq, reportByFunc) } func (cm comparer) String() string { - return fmt.Sprintf("Comparer(%s)", getFuncName(cm.fnc.Pointer())) + return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc)) } // AllowUnexported returns an Option that forcibly allows operations on @@ -338,7 +371,7 @@ func (cm comparer) String() string { // defined in an internal package where the semantic meaning of an unexported // field is in the control of the user. // -// For some cases, a custom Comparer should be used instead that defines +// In many cases, a custom Comparer should be used instead that defines // equality as a function of the public API of a type rather than the underlying // unexported implementation. // @@ -370,27 +403,92 @@ func AllowUnexported(types ...interface{}) Option { type visibleStructs map[reflect.Type]bool -func (visibleStructs) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { +func (visibleStructs) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { panic("not implemented") } -// reporter is an Option that configures how differences are reported. -type reporter interface { - // TODO: Not exported yet. +// Result represents the comparison result for a single node and +// is provided by cmp when calling Result (see Reporter). +type Result struct { + _ [0]func() // Make Result incomparable + flags resultFlags +} + +// Equal reports whether the node was determined to be equal or not. +// As a special case, ignored nodes are considered equal. +func (r Result) Equal() bool { + return r.flags&(reportEqual|reportByIgnore) != 0 +} + +// ByIgnore reports whether the node is equal because it was ignored. +// This never reports true if Equal reports false. +func (r Result) ByIgnore() bool { + return r.flags&reportByIgnore != 0 +} + +// ByMethod reports whether the Equal method determined equality. +func (r Result) ByMethod() bool { + return r.flags&reportByMethod != 0 +} + +// ByFunc reports whether a Comparer function determined equality. +func (r Result) ByFunc() bool { + return r.flags&reportByFunc != 0 +} + +type resultFlags uint + +const ( + _ resultFlags = (1 << iota) / 2 + + reportEqual + reportUnequal + reportByIgnore + reportByMethod + reportByFunc +) + +// Reporter is an Option that can be passed to Equal. When Equal traverses +// the value trees, it calls PushStep as it descends into each node in the +// tree and PopStep as it ascend out of the node. The leaves of the tree are +// either compared (determined to be equal or not equal) or ignored and reported +// as such by calling the Report method. +func Reporter(r interface { + // PushStep is called when a tree-traversal operation is performed. + // The PathStep itself is only valid until the step is popped. + // The PathStep.Values are valid for the duration of the entire traversal + // and must not be mutated. + // + // Equal always calls PushStep at the start to provide an operation-less + // PathStep used to report the root values. // - // Perhaps add PushStep and PopStep and change Report to only accept - // a PathStep instead of the full-path? Adding a PushStep and PopStep makes - // it clear that we are traversing the value tree in a depth-first-search - // manner, which has an effect on how values are printed. + // Within a slice, the exact set of inserted, removed, or modified elements + // is unspecified and may change in future implementations. + // The entries of a map are iterated through in an unspecified order. + PushStep(PathStep) + + // Report is called exactly once on leaf nodes to report whether the + // comparison identified the node as equal, unequal, or ignored. + // A leaf node is one that is immediately preceded by and followed by + // a pair of PushStep and PopStep calls. + Report(Result) + + // PopStep ascends back up the value tree. + // There is always a matching pop call for every push call. + PopStep() +}) Option { + return reporter{r} +} - Option +type reporter struct{ reporterIface } +type reporterIface interface { + PushStep(PathStep) + Report(Result) + PopStep() +} - // Report is called for every comparison made and will be provided with - // the two values being compared, the equality result, and the - // current path in the value tree. It is possible for x or y to be an - // invalid reflect.Value if one of the values is non-existent; - // which is possible with maps and slices. - Report(x, y reflect.Value, eq bool, p Path) +func (reporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { + panic("not implemented") } // normalizeOption normalizes the input options such that all Options groups @@ -424,30 +522,3 @@ func flattenOptions(dst, src Options) Options { } return dst } - -// getFuncName returns a short function name from the pointer. -// The string parsing logic works up until Go1.9. -func getFuncName(p uintptr) string { - fnc := runtime.FuncForPC(p) - if fnc == nil { - return "" - } - name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm" - if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") { - // Strip the package name from method name. - name = strings.TrimSuffix(name, ")-fm") - name = strings.TrimSuffix(name, ")·fm") - if i := strings.LastIndexByte(name, '('); i >= 0 { - methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc" - if j := strings.LastIndexByte(methodName, '.'); j >= 0 { - methodName = methodName[j+1:] // E.g., "myfunc" - } - name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc" - } - } - if i := strings.LastIndexByte(name, '/'); i >= 0 { - // Strip the package name. - name = name[i+1:] // E.g., "mypkg.(mytype).myfunc" - } - return name -} diff --git a/vendor/github.com/google/go-cmp/cmp/path.go b/vendor/github.com/google/go-cmp/cmp/path.go index c08a3cf80d9b..96fffd291f73 100644 --- a/vendor/github.com/google/go-cmp/cmp/path.go +++ b/vendor/github.com/google/go-cmp/cmp/path.go @@ -12,80 +12,52 @@ import ( "unicode/utf8" ) -type ( - // Path is a list of PathSteps describing the sequence of operations to get - // from some root type to the current position in the value tree. - // The first Path element is always an operation-less PathStep that exists - // simply to identify the initial type. - // - // When traversing structs with embedded structs, the embedded struct will - // always be accessed as a field before traversing the fields of the - // embedded struct themselves. That is, an exported field from the - // embedded struct will never be accessed directly from the parent struct. - Path []PathStep - - // PathStep is a union-type for specific operations to traverse - // a value's tree structure. Users of this package never need to implement - // these types as values of this type will be returned by this package. - PathStep interface { - String() string - Type() reflect.Type // Resulting type after performing the path step - isPathStep() - } +// Path is a list of PathSteps describing the sequence of operations to get +// from some root type to the current position in the value tree. +// The first Path element is always an operation-less PathStep that exists +// simply to identify the initial type. +// +// When traversing structs with embedded structs, the embedded struct will +// always be accessed as a field before traversing the fields of the +// embedded struct themselves. That is, an exported field from the +// embedded struct will never be accessed directly from the parent struct. +type Path []PathStep - // SliceIndex is an index operation on a slice or array at some index Key. - SliceIndex interface { - PathStep - Key() int // May return -1 if in a split state - - // SplitKeys returns the indexes for indexing into slices in the - // x and y values, respectively. These indexes may differ due to the - // insertion or removal of an element in one of the slices, causing - // all of the indexes to be shifted. If an index is -1, then that - // indicates that the element does not exist in the associated slice. - // - // Key is guaranteed to return -1 if and only if the indexes returned - // by SplitKeys are not the same. SplitKeys will never return -1 for - // both indexes. - SplitKeys() (x int, y int) - - isSliceIndex() - } - // MapIndex is an index operation on a map at some index Key. - MapIndex interface { - PathStep - Key() reflect.Value - isMapIndex() - } - // TypeAssertion represents a type assertion on an interface. - TypeAssertion interface { - PathStep - isTypeAssertion() - } - // StructField represents a struct field access on a field called Name. - StructField interface { - PathStep - Name() string - Index() int - isStructField() - } - // Indirect represents pointer indirection on the parent type. - Indirect interface { - PathStep - isIndirect() - } - // Transform is a transformation from the parent type to the current type. - Transform interface { - PathStep - Name() string - Func() reflect.Value +// PathStep is a union-type for specific operations to traverse +// a value's tree structure. Users of this package never need to implement +// these types as values of this type will be returned by this package. +// +// Implementations of this interface are +// StructField, SliceIndex, MapIndex, Indirect, TypeAssertion, and Transform. +type PathStep interface { + String() string - // Option returns the originally constructed Transformer option. - // The == operator can be used to detect the exact option used. - Option() Option + // Type is the resulting type after performing the path step. + Type() reflect.Type - isTransform() - } + // Values is the resulting values after performing the path step. + // The type of each valid value is guaranteed to be identical to Type. + // + // In some cases, one or both may be invalid or have restrictions: + // • For StructField, both are not interface-able if the current field + // is unexported and the struct type is not explicitly permitted by + // AllowUnexported to traverse unexported fields. + // • For SliceIndex, one may be invalid if an element is missing from + // either the x or y slice. + // • For MapIndex, one may be invalid if an entry is missing from + // either the x or y map. + // + // The provided values must not be mutated. + Values() (vx, vy reflect.Value) +} + +var ( + _ PathStep = StructField{} + _ PathStep = SliceIndex{} + _ PathStep = MapIndex{} + _ PathStep = Indirect{} + _ PathStep = TypeAssertion{} + _ PathStep = Transform{} ) func (pa *Path) push(s PathStep) { @@ -124,7 +96,7 @@ func (pa Path) Index(i int) PathStep { func (pa Path) String() string { var ss []string for _, s := range pa { - if _, ok := s.(*structField); ok { + if _, ok := s.(StructField); ok { ss = append(ss, s.String()) } } @@ -144,13 +116,13 @@ func (pa Path) GoString() string { nextStep = pa[i+1] } switch s := s.(type) { - case *indirect: + case Indirect: numIndirect++ pPre, pPost := "(", ")" switch nextStep.(type) { - case *indirect: + case Indirect: continue // Next step is indirection, so let them batch up - case *structField: + case StructField: numIndirect-- // Automatic indirection on struct fields case nil: pPre, pPost = "", "" // Last step; no need for parenthesis @@ -161,19 +133,10 @@ func (pa Path) GoString() string { } numIndirect = 0 continue - case *transform: + case Transform: ssPre = append(ssPre, s.trans.name+"(") ssPost = append(ssPost, ")") continue - case *typeAssertion: - // As a special-case, elide type assertions on anonymous types - // since they are typically generated dynamically and can be very - // verbose. For example, some transforms return interface{} because - // of Go's lack of generics, but typically take in and return the - // exact same concrete type. - if s.Type().PkgPath() == "" { - continue - } } ssPost = append(ssPost, s.String()) } @@ -183,44 +146,13 @@ func (pa Path) GoString() string { return strings.Join(ssPre, "") + strings.Join(ssPost, "") } -type ( - pathStep struct { - typ reflect.Type - } - - sliceIndex struct { - pathStep - xkey, ykey int - } - mapIndex struct { - pathStep - key reflect.Value - } - typeAssertion struct { - pathStep - } - structField struct { - pathStep - name string - idx int - - // These fields are used for forcibly accessing an unexported field. - // pvx, pvy, and field are only valid if unexported is true. - unexported bool - force bool // Forcibly allow visibility - pvx, pvy reflect.Value // Parent values - field reflect.StructField // Field information - } - indirect struct { - pathStep - } - transform struct { - pathStep - trans *transformer - } -) +type pathStep struct { + typ reflect.Type + vx, vy reflect.Value +} -func (ps pathStep) Type() reflect.Type { return ps.typ } +func (ps pathStep) Type() reflect.Type { return ps.typ } +func (ps pathStep) Values() (vx, vy reflect.Value) { return ps.vx, ps.vy } func (ps pathStep) String() string { if ps.typ == nil { return "" @@ -232,7 +164,54 @@ func (ps pathStep) String() string { return fmt.Sprintf("{%s}", s) } -func (si sliceIndex) String() string { +// StructField represents a struct field access on a field called Name. +type StructField struct{ *structField } +type structField struct { + pathStep + name string + idx int + + // These fields are used for forcibly accessing an unexported field. + // pvx, pvy, and field are only valid if unexported is true. + unexported bool + mayForce bool // Forcibly allow visibility + pvx, pvy reflect.Value // Parent values + field reflect.StructField // Field information +} + +func (sf StructField) Type() reflect.Type { return sf.typ } +func (sf StructField) Values() (vx, vy reflect.Value) { + if !sf.unexported { + return sf.vx, sf.vy // CanInterface reports true + } + + // Forcibly obtain read-write access to an unexported struct field. + if sf.mayForce { + vx = retrieveUnexportedField(sf.pvx, sf.field) + vy = retrieveUnexportedField(sf.pvy, sf.field) + return vx, vy // CanInterface reports true + } + return sf.vx, sf.vy // CanInterface reports false +} +func (sf StructField) String() string { return fmt.Sprintf(".%s", sf.name) } + +// Name is the field name. +func (sf StructField) Name() string { return sf.name } + +// Index is the index of the field in the parent struct type. +// See reflect.Type.Field. +func (sf StructField) Index() int { return sf.idx } + +// SliceIndex is an index operation on a slice or array at some index Key. +type SliceIndex struct{ *sliceIndex } +type sliceIndex struct { + pathStep + xkey, ykey int +} + +func (si SliceIndex) Type() reflect.Type { return si.typ } +func (si SliceIndex) Values() (vx, vy reflect.Value) { return si.vx, si.vy } +func (si SliceIndex) String() string { switch { case si.xkey == si.ykey: return fmt.Sprintf("[%d]", si.xkey) @@ -247,63 +226,83 @@ func (si sliceIndex) String() string { return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey) } } -func (mi mapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) } -func (ta typeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) } -func (sf structField) String() string { return fmt.Sprintf(".%s", sf.name) } -func (in indirect) String() string { return "*" } -func (tf transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) } -func (si sliceIndex) Key() int { +// Key is the index key; it may return -1 if in a split state +func (si SliceIndex) Key() int { if si.xkey != si.ykey { return -1 } return si.xkey } -func (si sliceIndex) SplitKeys() (x, y int) { return si.xkey, si.ykey } -func (mi mapIndex) Key() reflect.Value { return mi.key } -func (sf structField) Name() string { return sf.name } -func (sf structField) Index() int { return sf.idx } -func (tf transform) Name() string { return tf.trans.name } -func (tf transform) Func() reflect.Value { return tf.trans.fnc } -func (tf transform) Option() Option { return tf.trans } - -func (pathStep) isPathStep() {} -func (sliceIndex) isSliceIndex() {} -func (mapIndex) isMapIndex() {} -func (typeAssertion) isTypeAssertion() {} -func (structField) isStructField() {} -func (indirect) isIndirect() {} -func (transform) isTransform() {} -var ( - _ SliceIndex = sliceIndex{} - _ MapIndex = mapIndex{} - _ TypeAssertion = typeAssertion{} - _ StructField = structField{} - _ Indirect = indirect{} - _ Transform = transform{} - - _ PathStep = sliceIndex{} - _ PathStep = mapIndex{} - _ PathStep = typeAssertion{} - _ PathStep = structField{} - _ PathStep = indirect{} - _ PathStep = transform{} -) +// SplitKeys are the indexes for indexing into slices in the +// x and y values, respectively. These indexes may differ due to the +// insertion or removal of an element in one of the slices, causing +// all of the indexes to be shifted. If an index is -1, then that +// indicates that the element does not exist in the associated slice. +// +// Key is guaranteed to return -1 if and only if the indexes returned +// by SplitKeys are not the same. SplitKeys will never return -1 for +// both indexes. +func (si SliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey } + +// MapIndex is an index operation on a map at some index Key. +type MapIndex struct{ *mapIndex } +type mapIndex struct { + pathStep + key reflect.Value +} + +func (mi MapIndex) Type() reflect.Type { return mi.typ } +func (mi MapIndex) Values() (vx, vy reflect.Value) { return mi.vx, mi.vy } +func (mi MapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) } + +// Key is the value of the map key. +func (mi MapIndex) Key() reflect.Value { return mi.key } + +// Indirect represents pointer indirection on the parent type. +type Indirect struct{ *indirect } +type indirect struct { + pathStep +} + +func (in Indirect) Type() reflect.Type { return in.typ } +func (in Indirect) Values() (vx, vy reflect.Value) { return in.vx, in.vy } +func (in Indirect) String() string { return "*" } + +// TypeAssertion represents a type assertion on an interface. +type TypeAssertion struct{ *typeAssertion } +type typeAssertion struct { + pathStep +} + +func (ta TypeAssertion) Type() reflect.Type { return ta.typ } +func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy } +func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) } + +// Transform is a transformation from the parent type to the current type. +type Transform struct{ *transform } +type transform struct { + pathStep + trans *transformer +} + +func (tf Transform) Type() reflect.Type { return tf.typ } +func (tf Transform) Values() (vx, vy reflect.Value) { return tf.vx, tf.vy } +func (tf Transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) } + +// Name is the name of the Transformer. +func (tf Transform) Name() string { return tf.trans.name } + +// Func is the function pointer to the transformer function. +func (tf Transform) Func() reflect.Value { return tf.trans.fnc } + +// Option returns the originally constructed Transformer option. +// The == operator can be used to detect the exact option used. +func (tf Transform) Option() Option { return tf.trans } // isExported reports whether the identifier is exported. func isExported(id string) bool { r, _ := utf8.DecodeRuneInString(id) return unicode.IsUpper(r) } - -// isValid reports whether the identifier is valid. -// Empty and underscore-only strings are not valid. -func isValid(id string) bool { - ok := id != "" && id != "_" - for j, c := range id { - ok = ok && (j > 0 || !unicode.IsDigit(c)) - ok = ok && (c == '_' || unicode.IsLetter(c) || unicode.IsDigit(c)) - } - return ok -} diff --git a/vendor/github.com/google/go-cmp/cmp/report.go b/vendor/github.com/google/go-cmp/cmp/report.go new file mode 100644 index 000000000000..6ddf29993e52 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/report.go @@ -0,0 +1,51 @@ +// Copyright 2017, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmp + +// defaultReporter implements the reporter interface. +// +// As Equal serially calls the PushStep, Report, and PopStep methods, the +// defaultReporter constructs a tree-based representation of the compared value +// and the result of each comparison (see valueNode). +// +// When the String method is called, the FormatDiff method transforms the +// valueNode tree into a textNode tree, which is a tree-based representation +// of the textual output (see textNode). +// +// Lastly, the textNode.String method produces the final report as a string. +type defaultReporter struct { + root *valueNode + curr *valueNode +} + +func (r *defaultReporter) PushStep(ps PathStep) { + r.curr = r.curr.PushStep(ps) + if r.root == nil { + r.root = r.curr + } +} +func (r *defaultReporter) Report(rs Result) { + r.curr.Report(rs) +} +func (r *defaultReporter) PopStep() { + r.curr = r.curr.PopStep() +} + +// String provides a full report of the differences detected as a structured +// literal in pseudo-Go syntax. String may only be called after the entire tree +// has been traversed. +func (r *defaultReporter) String() string { + assert(r.root != nil && r.curr == nil) + if r.root.NumDiff == 0 { + return "" + } + return formatOptions{}.FormatDiff(r.root).String() +} + +func assert(ok bool) { + if !ok { + panic("assertion failure") + } +} diff --git a/vendor/github.com/google/go-cmp/cmp/report_compare.go b/vendor/github.com/google/go-cmp/cmp/report_compare.go new file mode 100644 index 000000000000..05efb992c535 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/report_compare.go @@ -0,0 +1,296 @@ +// Copyright 2019, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmp + +import ( + "fmt" + "reflect" + + "github.com/google/go-cmp/cmp/internal/value" +) + +// TODO: Enforce limits? +// * Enforce maximum number of records to print per node? +// * Enforce maximum size in bytes allowed? +// * As a heuristic, use less verbosity for equal nodes than unequal nodes. +// TODO: Enforce unique outputs? +// * Avoid Stringer methods if it results in same output? +// * Print pointer address if outputs still equal? + +// numContextRecords is the number of surrounding equal records to print. +const numContextRecords = 2 + +type diffMode byte + +const ( + diffUnknown diffMode = 0 + diffIdentical diffMode = ' ' + diffRemoved diffMode = '-' + diffInserted diffMode = '+' +) + +type typeMode int + +const ( + // emitType always prints the type. + emitType typeMode = iota + // elideType never prints the type. + elideType + // autoType prints the type only for composite kinds + // (i.e., structs, slices, arrays, and maps). + autoType +) + +type formatOptions struct { + // DiffMode controls the output mode of FormatDiff. + // + // If diffUnknown, then produce a diff of the x and y values. + // If diffIdentical, then emit values as if they were equal. + // If diffRemoved, then only emit x values (ignoring y values). + // If diffInserted, then only emit y values (ignoring x values). + DiffMode diffMode + + // TypeMode controls whether to print the type for the current node. + // + // As a general rule of thumb, we always print the type of the next node + // after an interface, and always elide the type of the next node after + // a slice or map node. + TypeMode typeMode + + // formatValueOptions are options specific to printing reflect.Values. + formatValueOptions +} + +func (opts formatOptions) WithDiffMode(d diffMode) formatOptions { + opts.DiffMode = d + return opts +} +func (opts formatOptions) WithTypeMode(t typeMode) formatOptions { + opts.TypeMode = t + return opts +} + +// FormatDiff converts a valueNode tree into a textNode tree, where the later +// is a textual representation of the differences detected in the former. +func (opts formatOptions) FormatDiff(v *valueNode) textNode { + // Check whether we have specialized formatting for this node. + // This is not necessary, but helpful for producing more readable outputs. + if opts.CanFormatDiffSlice(v) { + return opts.FormatDiffSlice(v) + } + + // For leaf nodes, format the value based on the reflect.Values alone. + if v.MaxDepth == 0 { + switch opts.DiffMode { + case diffUnknown, diffIdentical: + // Format Equal. + if v.NumDiff == 0 { + outx := opts.FormatValue(v.ValueX, visitedPointers{}) + outy := opts.FormatValue(v.ValueY, visitedPointers{}) + if v.NumIgnored > 0 && v.NumSame == 0 { + return textEllipsis + } else if outx.Len() < outy.Len() { + return outx + } else { + return outy + } + } + + // Format unequal. + assert(opts.DiffMode == diffUnknown) + var list textList + outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, visitedPointers{}) + outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, visitedPointers{}) + if outx != nil { + list = append(list, textRecord{Diff: '-', Value: outx}) + } + if outy != nil { + list = append(list, textRecord{Diff: '+', Value: outy}) + } + return opts.WithTypeMode(emitType).FormatType(v.Type, list) + case diffRemoved: + return opts.FormatValue(v.ValueX, visitedPointers{}) + case diffInserted: + return opts.FormatValue(v.ValueY, visitedPointers{}) + default: + panic("invalid diff mode") + } + } + + // Descend into the child value node. + if v.TransformerName != "" { + out := opts.WithTypeMode(emitType).FormatDiff(v.Value) + out = textWrap{"Inverse(" + v.TransformerName + ", ", out, ")"} + return opts.FormatType(v.Type, out) + } else { + switch k := v.Type.Kind(); k { + case reflect.Struct, reflect.Array, reflect.Slice, reflect.Map: + return opts.FormatType(v.Type, opts.formatDiffList(v.Records, k)) + case reflect.Ptr: + return textWrap{"&", opts.FormatDiff(v.Value), ""} + case reflect.Interface: + return opts.WithTypeMode(emitType).FormatDiff(v.Value) + default: + panic(fmt.Sprintf("%v cannot have children", k)) + } + } +} + +func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) textNode { + // Derive record name based on the data structure kind. + var name string + var formatKey func(reflect.Value) string + switch k { + case reflect.Struct: + name = "field" + opts = opts.WithTypeMode(autoType) + formatKey = func(v reflect.Value) string { return v.String() } + case reflect.Slice, reflect.Array: + name = "element" + opts = opts.WithTypeMode(elideType) + formatKey = func(reflect.Value) string { return "" } + case reflect.Map: + name = "entry" + opts = opts.WithTypeMode(elideType) + formatKey = formatMapKey + } + + // Handle unification. + switch opts.DiffMode { + case diffIdentical, diffRemoved, diffInserted: + var list textList + var deferredEllipsis bool // Add final "..." to indicate records were dropped + for _, r := range recs { + // Elide struct fields that are zero value. + if k == reflect.Struct { + var isZero bool + switch opts.DiffMode { + case diffIdentical: + isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueX) + case diffRemoved: + isZero = value.IsZero(r.Value.ValueX) + case diffInserted: + isZero = value.IsZero(r.Value.ValueY) + } + if isZero { + continue + } + } + // Elide ignored nodes. + if r.Value.NumIgnored > 0 && r.Value.NumSame+r.Value.NumDiff == 0 { + deferredEllipsis = !(k == reflect.Slice || k == reflect.Array) + if !deferredEllipsis { + list.AppendEllipsis(diffStats{}) + } + continue + } + if out := opts.FormatDiff(r.Value); out != nil { + list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) + } + } + if deferredEllipsis { + list.AppendEllipsis(diffStats{}) + } + return textWrap{"{", list, "}"} + case diffUnknown: + default: + panic("invalid diff mode") + } + + // Handle differencing. + var list textList + groups := coalesceAdjacentRecords(name, recs) + for i, ds := range groups { + // Handle equal records. + if ds.NumDiff() == 0 { + // Compute the number of leading and trailing records to print. + var numLo, numHi int + numEqual := ds.NumIgnored + ds.NumIdentical + for numLo < numContextRecords && numLo+numHi < numEqual && i != 0 { + if r := recs[numLo].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 { + break + } + numLo++ + } + for numHi < numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 { + if r := recs[numEqual-numHi-1].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 { + break + } + numHi++ + } + if numEqual-(numLo+numHi) == 1 && ds.NumIgnored == 0 { + numHi++ // Avoid pointless coalescing of a single equal record + } + + // Format the equal values. + for _, r := range recs[:numLo] { + out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value) + list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) + } + if numEqual > numLo+numHi { + ds.NumIdentical -= numLo + numHi + list.AppendEllipsis(ds) + } + for _, r := range recs[numEqual-numHi : numEqual] { + out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value) + list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) + } + recs = recs[numEqual:] + continue + } + + // Handle unequal records. + for _, r := range recs[:ds.NumDiff()] { + switch { + case opts.CanFormatDiffSlice(r.Value): + out := opts.FormatDiffSlice(r.Value) + list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) + case r.Value.NumChildren == r.Value.MaxDepth: + outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value) + outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value) + if outx != nil { + list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx}) + } + if outy != nil { + list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy}) + } + default: + out := opts.FormatDiff(r.Value) + list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) + } + } + recs = recs[ds.NumDiff():] + } + assert(len(recs) == 0) + return textWrap{"{", list, "}"} +} + +// coalesceAdjacentRecords coalesces the list of records into groups of +// adjacent equal, or unequal counts. +func coalesceAdjacentRecords(name string, recs []reportRecord) (groups []diffStats) { + var prevCase int // Arbitrary index into which case last occurred + lastStats := func(i int) *diffStats { + if prevCase != i { + groups = append(groups, diffStats{Name: name}) + prevCase = i + } + return &groups[len(groups)-1] + } + for _, r := range recs { + switch rv := r.Value; { + case rv.NumIgnored > 0 && rv.NumSame+rv.NumDiff == 0: + lastStats(1).NumIgnored++ + case rv.NumDiff == 0: + lastStats(1).NumIdentical++ + case rv.NumDiff > 0 && !rv.ValueY.IsValid(): + lastStats(2).NumRemoved++ + case rv.NumDiff > 0 && !rv.ValueX.IsValid(): + lastStats(2).NumInserted++ + default: + lastStats(2).NumModified++ + } + } + return groups +} diff --git a/vendor/github.com/google/go-cmp/cmp/report_reflect.go b/vendor/github.com/google/go-cmp/cmp/report_reflect.go new file mode 100644 index 000000000000..5521c604c546 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/report_reflect.go @@ -0,0 +1,279 @@ +// Copyright 2019, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmp + +import ( + "fmt" + "reflect" + "strconv" + "strings" + "unicode" + + "github.com/google/go-cmp/cmp/internal/flags" + "github.com/google/go-cmp/cmp/internal/value" +) + +type formatValueOptions struct { + // AvoidStringer controls whether to avoid calling custom stringer + // methods like error.Error or fmt.Stringer.String. + AvoidStringer bool + + // ShallowPointers controls whether to avoid descending into pointers. + // Useful when printing map keys, where pointer comparison is performed + // on the pointer address rather than the pointed-at value. + ShallowPointers bool + + // PrintAddresses controls whether to print the address of all pointers, + // slice elements, and maps. + PrintAddresses bool +} + +// FormatType prints the type as if it were wrapping s. +// This may return s as-is depending on the current type and TypeMode mode. +func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode { + // Check whether to emit the type or not. + switch opts.TypeMode { + case autoType: + switch t.Kind() { + case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map: + if s.Equal(textNil) { + return s + } + default: + return s + } + case elideType: + return s + } + + // Determine the type label, applying special handling for unnamed types. + typeName := t.String() + if t.Name() == "" { + // According to Go grammar, certain type literals contain symbols that + // do not strongly bind to the next lexicographical token (e.g., *T). + switch t.Kind() { + case reflect.Chan, reflect.Func, reflect.Ptr: + typeName = "(" + typeName + ")" + } + typeName = strings.Replace(typeName, "struct {", "struct{", -1) + typeName = strings.Replace(typeName, "interface {", "interface{", -1) + } + + // Avoid wrap the value in parenthesis if unnecessary. + if s, ok := s.(textWrap); ok { + hasParens := strings.HasPrefix(s.Prefix, "(") && strings.HasSuffix(s.Suffix, ")") + hasBraces := strings.HasPrefix(s.Prefix, "{") && strings.HasSuffix(s.Suffix, "}") + if hasParens || hasBraces { + return textWrap{typeName, s, ""} + } + } + return textWrap{typeName + "(", s, ")"} +} + +// FormatValue prints the reflect.Value, taking extra care to avoid descending +// into pointers already in m. As pointers are visited, m is also updated. +func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out textNode) { + if !v.IsValid() { + return nil + } + t := v.Type() + + // Check whether there is an Error or String method to call. + if !opts.AvoidStringer && v.CanInterface() { + // Avoid calling Error or String methods on nil receivers since many + // implementations crash when doing so. + if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() { + switch v := v.Interface().(type) { + case error: + return textLine("e" + formatString(v.Error())) + case fmt.Stringer: + return textLine("s" + formatString(v.String())) + } + } + } + + // Check whether to explicitly wrap the result with the type. + var skipType bool + defer func() { + if !skipType { + out = opts.FormatType(t, out) + } + }() + + var ptr string + switch t.Kind() { + case reflect.Bool: + return textLine(fmt.Sprint(v.Bool())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return textLine(fmt.Sprint(v.Int())) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + // Unnamed uints are usually bytes or words, so use hexadecimal. + if t.PkgPath() == "" || t.Kind() == reflect.Uintptr { + return textLine(formatHex(v.Uint())) + } + return textLine(fmt.Sprint(v.Uint())) + case reflect.Float32, reflect.Float64: + return textLine(fmt.Sprint(v.Float())) + case reflect.Complex64, reflect.Complex128: + return textLine(fmt.Sprint(v.Complex())) + case reflect.String: + return textLine(formatString(v.String())) + case reflect.UnsafePointer, reflect.Chan, reflect.Func: + return textLine(formatPointer(v)) + case reflect.Struct: + var list textList + for i := 0; i < v.NumField(); i++ { + vv := v.Field(i) + if value.IsZero(vv) { + continue // Elide fields with zero values + } + s := opts.WithTypeMode(autoType).FormatValue(vv, m) + list = append(list, textRecord{Key: t.Field(i).Name, Value: s}) + } + return textWrap{"{", list, "}"} + case reflect.Slice: + if v.IsNil() { + return textNil + } + if opts.PrintAddresses { + ptr = formatPointer(v) + } + fallthrough + case reflect.Array: + var list textList + for i := 0; i < v.Len(); i++ { + vi := v.Index(i) + if vi.CanAddr() { // Check for cyclic elements + p := vi.Addr() + if m.Visit(p) { + var out textNode + out = textLine(formatPointer(p)) + out = opts.WithTypeMode(emitType).FormatType(p.Type(), out) + out = textWrap{"*", out, ""} + list = append(list, textRecord{Value: out}) + continue + } + } + s := opts.WithTypeMode(elideType).FormatValue(vi, m) + list = append(list, textRecord{Value: s}) + } + return textWrap{ptr + "{", list, "}"} + case reflect.Map: + if v.IsNil() { + return textNil + } + if m.Visit(v) { + return textLine(formatPointer(v)) + } + + var list textList + for _, k := range value.SortKeys(v.MapKeys()) { + sk := formatMapKey(k) + sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), m) + list = append(list, textRecord{Key: sk, Value: sv}) + } + if opts.PrintAddresses { + ptr = formatPointer(v) + } + return textWrap{ptr + "{", list, "}"} + case reflect.Ptr: + if v.IsNil() { + return textNil + } + if m.Visit(v) || opts.ShallowPointers { + return textLine(formatPointer(v)) + } + if opts.PrintAddresses { + ptr = formatPointer(v) + } + skipType = true // Let the underlying value print the type instead + return textWrap{"&" + ptr, opts.FormatValue(v.Elem(), m), ""} + case reflect.Interface: + if v.IsNil() { + return textNil + } + // Interfaces accept different concrete types, + // so configure the underlying value to explicitly print the type. + skipType = true // Print the concrete type instead + return opts.WithTypeMode(emitType).FormatValue(v.Elem(), m) + default: + panic(fmt.Sprintf("%v kind not handled", v.Kind())) + } +} + +// formatMapKey formats v as if it were a map key. +// The result is guaranteed to be a single line. +func formatMapKey(v reflect.Value) string { + var opts formatOptions + opts.TypeMode = elideType + opts.AvoidStringer = true + opts.ShallowPointers = true + s := opts.FormatValue(v, visitedPointers{}).String() + return strings.TrimSpace(s) +} + +// formatString prints s as a double-quoted or backtick-quoted string. +func formatString(s string) string { + // Use quoted string if it the same length as a raw string literal. + // Otherwise, attempt to use the raw string form. + qs := strconv.Quote(s) + if len(qs) == 1+len(s)+1 { + return qs + } + + // Disallow newlines to ensure output is a single line. + // Only allow printable runes for readability purposes. + rawInvalid := func(r rune) bool { + return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t') + } + if strings.IndexFunc(s, rawInvalid) < 0 { + return "`" + s + "`" + } + return qs +} + +// formatHex prints u as a hexadecimal integer in Go notation. +func formatHex(u uint64) string { + var f string + switch { + case u <= 0xff: + f = "0x%02x" + case u <= 0xffff: + f = "0x%04x" + case u <= 0xffffff: + f = "0x%06x" + case u <= 0xffffffff: + f = "0x%08x" + case u <= 0xffffffffff: + f = "0x%010x" + case u <= 0xffffffffffff: + f = "0x%012x" + case u <= 0xffffffffffffff: + f = "0x%014x" + case u <= 0xffffffffffffffff: + f = "0x%016x" + } + return fmt.Sprintf(f, u) +} + +// formatPointer prints the address of the pointer. +func formatPointer(v reflect.Value) string { + p := v.Pointer() + if flags.Deterministic { + p = 0xdeadf00f // Only used for stable testing purposes + } + return fmt.Sprintf("⟪0x%x⟫", p) +} + +type visitedPointers map[value.Pointer]struct{} + +// Visit inserts pointer v into the visited map and reports whether it had +// already been visited before. +func (m visitedPointers) Visit(v reflect.Value) bool { + p := value.PointerOf(v) + _, visited := m[p] + m[p] = struct{}{} + return visited +} diff --git a/vendor/github.com/google/go-cmp/cmp/report_slices.go b/vendor/github.com/google/go-cmp/cmp/report_slices.go new file mode 100644 index 000000000000..8cb3265e767e --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/report_slices.go @@ -0,0 +1,333 @@ +// Copyright 2019, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmp + +import ( + "bytes" + "fmt" + "reflect" + "strings" + "unicode" + "unicode/utf8" + + "github.com/google/go-cmp/cmp/internal/diff" +) + +// CanFormatDiffSlice reports whether we support custom formatting for nodes +// that are slices of primitive kinds or strings. +func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { + switch { + case opts.DiffMode != diffUnknown: + return false // Must be formatting in diff mode + case v.NumDiff == 0: + return false // No differences detected + case v.NumIgnored+v.NumCompared+v.NumTransformed > 0: + // TODO: Handle the case where someone uses bytes.Equal on a large slice. + return false // Some custom option was used to determined equality + case !v.ValueX.IsValid() || !v.ValueY.IsValid(): + return false // Both values must be valid + } + + switch t := v.Type; t.Kind() { + case reflect.String: + case reflect.Array, reflect.Slice: + // Only slices of primitive types have specialized handling. + switch t.Elem().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, + reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: + default: + return false + } + + // If a sufficient number of elements already differ, + // use specialized formatting even if length requirement is not met. + if v.NumDiff > v.NumSame { + return true + } + default: + return false + } + + // Use specialized string diffing for longer slices or strings. + const minLength = 64 + return v.ValueX.Len() >= minLength && v.ValueY.Len() >= minLength +} + +// FormatDiffSlice prints a diff for the slices (or strings) represented by v. +// This provides custom-tailored logic to make printing of differences in +// textual strings and slices of primitive kinds more readable. +func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { + assert(opts.DiffMode == diffUnknown) + t, vx, vy := v.Type, v.ValueX, v.ValueY + + // Auto-detect the type of the data. + var isLinedText, isText, isBinary bool + var sx, sy string + switch { + case t.Kind() == reflect.String: + sx, sy = vx.String(), vy.String() + isText = true // Initial estimate, verify later + case t.Kind() == reflect.Slice && t.Elem() == reflect.TypeOf(byte(0)): + sx, sy = string(vx.Bytes()), string(vy.Bytes()) + isBinary = true // Initial estimate, verify later + case t.Kind() == reflect.Array: + // Arrays need to be addressable for slice operations to work. + vx2, vy2 := reflect.New(t).Elem(), reflect.New(t).Elem() + vx2.Set(vx) + vy2.Set(vy) + vx, vy = vx2, vy2 + } + if isText || isBinary { + var numLines, lastLineIdx, maxLineLen int + isBinary = false + for i, r := range sx + sy { + if !(unicode.IsPrint(r) || unicode.IsSpace(r)) || r == utf8.RuneError { + isBinary = true + break + } + if r == '\n' { + if maxLineLen < i-lastLineIdx { + lastLineIdx = i - lastLineIdx + } + lastLineIdx = i + 1 + numLines++ + } + } + isText = !isBinary + isLinedText = isText && numLines >= 4 && maxLineLen <= 256 + } + + // Format the string into printable records. + var list textList + var delim string + switch { + // If the text appears to be multi-lined text, + // then perform differencing across individual lines. + case isLinedText: + ssx := strings.Split(sx, "\n") + ssy := strings.Split(sy, "\n") + list = opts.formatDiffSlice( + reflect.ValueOf(ssx), reflect.ValueOf(ssy), 1, "line", + func(v reflect.Value, d diffMode) textRecord { + s := formatString(v.Index(0).String()) + return textRecord{Diff: d, Value: textLine(s)} + }, + ) + delim = "\n" + // If the text appears to be single-lined text, + // then perform differencing in approximately fixed-sized chunks. + // The output is printed as quoted strings. + case isText: + list = opts.formatDiffSlice( + reflect.ValueOf(sx), reflect.ValueOf(sy), 64, "byte", + func(v reflect.Value, d diffMode) textRecord { + s := formatString(v.String()) + return textRecord{Diff: d, Value: textLine(s)} + }, + ) + delim = "" + // If the text appears to be binary data, + // then perform differencing in approximately fixed-sized chunks. + // The output is inspired by hexdump. + case isBinary: + list = opts.formatDiffSlice( + reflect.ValueOf(sx), reflect.ValueOf(sy), 16, "byte", + func(v reflect.Value, d diffMode) textRecord { + var ss []string + for i := 0; i < v.Len(); i++ { + ss = append(ss, formatHex(v.Index(i).Uint())) + } + s := strings.Join(ss, ", ") + comment := commentString(fmt.Sprintf("%c|%v|", d, formatASCII(v.String()))) + return textRecord{Diff: d, Value: textLine(s), Comment: comment} + }, + ) + // For all other slices of primitive types, + // then perform differencing in approximately fixed-sized chunks. + // The size of each chunk depends on the width of the element kind. + default: + var chunkSize int + if t.Elem().Kind() == reflect.Bool { + chunkSize = 16 + } else { + switch t.Elem().Bits() { + case 8: + chunkSize = 16 + case 16: + chunkSize = 12 + case 32: + chunkSize = 8 + default: + chunkSize = 8 + } + } + list = opts.formatDiffSlice( + vx, vy, chunkSize, t.Elem().Kind().String(), + func(v reflect.Value, d diffMode) textRecord { + var ss []string + for i := 0; i < v.Len(); i++ { + switch t.Elem().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + ss = append(ss, fmt.Sprint(v.Index(i).Int())) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + ss = append(ss, formatHex(v.Index(i).Uint())) + case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: + ss = append(ss, fmt.Sprint(v.Index(i).Interface())) + } + } + s := strings.Join(ss, ", ") + return textRecord{Diff: d, Value: textLine(s)} + }, + ) + } + + // Wrap the output with appropriate type information. + var out textNode = textWrap{"{", list, "}"} + if !isText { + // The "{...}" byte-sequence literal is not valid Go syntax for strings. + // Emit the type for extra clarity (e.g. "string{...}"). + if t.Kind() == reflect.String { + opts = opts.WithTypeMode(emitType) + } + return opts.FormatType(t, out) + } + switch t.Kind() { + case reflect.String: + out = textWrap{"strings.Join(", out, fmt.Sprintf(", %q)", delim)} + if t != reflect.TypeOf(string("")) { + out = opts.FormatType(t, out) + } + case reflect.Slice: + out = textWrap{"bytes.Join(", out, fmt.Sprintf(", %q)", delim)} + if t != reflect.TypeOf([]byte(nil)) { + out = opts.FormatType(t, out) + } + } + return out +} + +// formatASCII formats s as an ASCII string. +// This is useful for printing binary strings in a semi-legible way. +func formatASCII(s string) string { + b := bytes.Repeat([]byte{'.'}, len(s)) + for i := 0; i < len(s); i++ { + if ' ' <= s[i] && s[i] <= '~' { + b[i] = s[i] + } + } + return string(b) +} + +func (opts formatOptions) formatDiffSlice( + vx, vy reflect.Value, chunkSize int, name string, + makeRec func(reflect.Value, diffMode) textRecord, +) (list textList) { + es := diff.Difference(vx.Len(), vy.Len(), func(ix int, iy int) diff.Result { + return diff.BoolResult(vx.Index(ix).Interface() == vy.Index(iy).Interface()) + }) + + appendChunks := func(v reflect.Value, d diffMode) int { + n0 := v.Len() + for v.Len() > 0 { + n := chunkSize + if n > v.Len() { + n = v.Len() + } + list = append(list, makeRec(v.Slice(0, n), d)) + v = v.Slice(n, v.Len()) + } + return n0 - v.Len() + } + + groups := coalesceAdjacentEdits(name, es) + groups = coalesceInterveningIdentical(groups, chunkSize/4) + for i, ds := range groups { + // Print equal. + if ds.NumDiff() == 0 { + // Compute the number of leading and trailing equal bytes to print. + var numLo, numHi int + numEqual := ds.NumIgnored + ds.NumIdentical + for numLo < chunkSize*numContextRecords && numLo+numHi < numEqual && i != 0 { + numLo++ + } + for numHi < chunkSize*numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 { + numHi++ + } + if numEqual-(numLo+numHi) <= chunkSize && ds.NumIgnored == 0 { + numHi = numEqual - numLo // Avoid pointless coalescing of single equal row + } + + // Print the equal bytes. + appendChunks(vx.Slice(0, numLo), diffIdentical) + if numEqual > numLo+numHi { + ds.NumIdentical -= numLo + numHi + list.AppendEllipsis(ds) + } + appendChunks(vx.Slice(numEqual-numHi, numEqual), diffIdentical) + vx = vx.Slice(numEqual, vx.Len()) + vy = vy.Slice(numEqual, vy.Len()) + continue + } + + // Print unequal. + nx := appendChunks(vx.Slice(0, ds.NumIdentical+ds.NumRemoved+ds.NumModified), diffRemoved) + vx = vx.Slice(nx, vx.Len()) + ny := appendChunks(vy.Slice(0, ds.NumIdentical+ds.NumInserted+ds.NumModified), diffInserted) + vy = vy.Slice(ny, vy.Len()) + } + assert(vx.Len() == 0 && vy.Len() == 0) + return list +} + +// coalesceAdjacentEdits coalesces the list of edits into groups of adjacent +// equal or unequal counts. +func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) { + var prevCase int // Arbitrary index into which case last occurred + lastStats := func(i int) *diffStats { + if prevCase != i { + groups = append(groups, diffStats{Name: name}) + prevCase = i + } + return &groups[len(groups)-1] + } + for _, e := range es { + switch e { + case diff.Identity: + lastStats(1).NumIdentical++ + case diff.UniqueX: + lastStats(2).NumRemoved++ + case diff.UniqueY: + lastStats(2).NumInserted++ + case diff.Modified: + lastStats(2).NumModified++ + } + } + return groups +} + +// coalesceInterveningIdentical coalesces sufficiently short (<= windowSize) +// equal groups into adjacent unequal groups that currently result in a +// dual inserted/removed printout. This acts as a high-pass filter to smooth +// out high-frequency changes within the windowSize. +func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats { + groups, groupsOrig := groups[:0], groups + for i, ds := range groupsOrig { + if len(groups) >= 2 && ds.NumDiff() > 0 { + prev := &groups[len(groups)-2] // Unequal group + curr := &groups[len(groups)-1] // Equal group + next := &groupsOrig[i] // Unequal group + hadX, hadY := prev.NumRemoved > 0, prev.NumInserted > 0 + hasX, hasY := next.NumRemoved > 0, next.NumInserted > 0 + if ((hadX || hasX) && (hadY || hasY)) && curr.NumIdentical <= windowSize { + *prev = (*prev).Append(*curr).Append(*next) + groups = groups[:len(groups)-1] // Truncate off equal group + continue + } + } + groups = append(groups, ds) + } + return groups +} diff --git a/vendor/github.com/google/go-cmp/cmp/report_text.go b/vendor/github.com/google/go-cmp/cmp/report_text.go new file mode 100644 index 000000000000..80605d0e440b --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/report_text.go @@ -0,0 +1,382 @@ +// Copyright 2019, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmp + +import ( + "bytes" + "fmt" + "math/rand" + "strings" + "time" + + "github.com/google/go-cmp/cmp/internal/flags" +) + +var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0 + +type indentMode int + +func (n indentMode) appendIndent(b []byte, d diffMode) []byte { + if flags.Deterministic || randBool { + // Use regular spaces (U+0020). + switch d { + case diffUnknown, diffIdentical: + b = append(b, " "...) + case diffRemoved: + b = append(b, "- "...) + case diffInserted: + b = append(b, "+ "...) + } + } else { + // Use non-breaking spaces (U+00a0). + switch d { + case diffUnknown, diffIdentical: + b = append(b, "  "...) + case diffRemoved: + b = append(b, "- "...) + case diffInserted: + b = append(b, "+ "...) + } + } + return repeatCount(n).appendChar(b, '\t') +} + +type repeatCount int + +func (n repeatCount) appendChar(b []byte, c byte) []byte { + for ; n > 0; n-- { + b = append(b, c) + } + return b +} + +// textNode is a simplified tree-based representation of structured text. +// Possible node types are textWrap, textList, or textLine. +type textNode interface { + // Len reports the length in bytes of a single-line version of the tree. + // Nested textRecord.Diff and textRecord.Comment fields are ignored. + Len() int + // Equal reports whether the two trees are structurally identical. + // Nested textRecord.Diff and textRecord.Comment fields are compared. + Equal(textNode) bool + // String returns the string representation of the text tree. + // It is not guaranteed that len(x.String()) == x.Len(), + // nor that x.String() == y.String() implies that x.Equal(y). + String() string + + // formatCompactTo formats the contents of the tree as a single-line string + // to the provided buffer. Any nested textRecord.Diff and textRecord.Comment + // fields are ignored. + // + // However, not all nodes in the tree should be collapsed as a single-line. + // If a node can be collapsed as a single-line, it is replaced by a textLine + // node. Since the top-level node cannot replace itself, this also returns + // the current node itself. + // + // This does not mutate the receiver. + formatCompactTo([]byte, diffMode) ([]byte, textNode) + // formatExpandedTo formats the contents of the tree as a multi-line string + // to the provided buffer. In order for column alignment to operate well, + // formatCompactTo must be called before calling formatExpandedTo. + formatExpandedTo([]byte, diffMode, indentMode) []byte +} + +// textWrap is a wrapper that concatenates a prefix and/or a suffix +// to the underlying node. +type textWrap struct { + Prefix string // e.g., "bytes.Buffer{" + Value textNode // textWrap | textList | textLine + Suffix string // e.g., "}" +} + +func (s textWrap) Len() int { + return len(s.Prefix) + s.Value.Len() + len(s.Suffix) +} +func (s1 textWrap) Equal(s2 textNode) bool { + if s2, ok := s2.(textWrap); ok { + return s1.Prefix == s2.Prefix && s1.Value.Equal(s2.Value) && s1.Suffix == s2.Suffix + } + return false +} +func (s textWrap) String() string { + var d diffMode + var n indentMode + _, s2 := s.formatCompactTo(nil, d) + b := n.appendIndent(nil, d) // Leading indent + b = s2.formatExpandedTo(b, d, n) // Main body + b = append(b, '\n') // Trailing newline + return string(b) +} +func (s textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) { + n0 := len(b) // Original buffer length + b = append(b, s.Prefix...) + b, s.Value = s.Value.formatCompactTo(b, d) + b = append(b, s.Suffix...) + if _, ok := s.Value.(textLine); ok { + return b, textLine(b[n0:]) + } + return b, s +} +func (s textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte { + b = append(b, s.Prefix...) + b = s.Value.formatExpandedTo(b, d, n) + b = append(b, s.Suffix...) + return b +} + +// textList is a comma-separated list of textWrap or textLine nodes. +// The list may be formatted as multi-lines or single-line at the discretion +// of the textList.formatCompactTo method. +type textList []textRecord +type textRecord struct { + Diff diffMode // e.g., 0 or '-' or '+' + Key string // e.g., "MyField" + Value textNode // textWrap | textLine + Comment fmt.Stringer // e.g., "6 identical fields" +} + +// AppendEllipsis appends a new ellipsis node to the list if none already +// exists at the end. If cs is non-zero it coalesces the statistics with the +// previous diffStats. +func (s *textList) AppendEllipsis(ds diffStats) { + hasStats := ds != diffStats{} + if len(*s) == 0 || !(*s)[len(*s)-1].Value.Equal(textEllipsis) { + if hasStats { + *s = append(*s, textRecord{Value: textEllipsis, Comment: ds}) + } else { + *s = append(*s, textRecord{Value: textEllipsis}) + } + return + } + if hasStats { + (*s)[len(*s)-1].Comment = (*s)[len(*s)-1].Comment.(diffStats).Append(ds) + } +} + +func (s textList) Len() (n int) { + for i, r := range s { + n += len(r.Key) + if r.Key != "" { + n += len(": ") + } + n += r.Value.Len() + if i < len(s)-1 { + n += len(", ") + } + } + return n +} + +func (s1 textList) Equal(s2 textNode) bool { + if s2, ok := s2.(textList); ok { + if len(s1) != len(s2) { + return false + } + for i := range s1 { + r1, r2 := s1[i], s2[i] + if !(r1.Diff == r2.Diff && r1.Key == r2.Key && r1.Value.Equal(r2.Value) && r1.Comment == r2.Comment) { + return false + } + } + return true + } + return false +} + +func (s textList) String() string { + return textWrap{"{", s, "}"}.String() +} + +func (s textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) { + s = append(textList(nil), s...) // Avoid mutating original + + // Determine whether we can collapse this list as a single line. + n0 := len(b) // Original buffer length + var multiLine bool + for i, r := range s { + if r.Diff == diffInserted || r.Diff == diffRemoved { + multiLine = true + } + b = append(b, r.Key...) + if r.Key != "" { + b = append(b, ": "...) + } + b, s[i].Value = r.Value.formatCompactTo(b, d|r.Diff) + if _, ok := s[i].Value.(textLine); !ok { + multiLine = true + } + if r.Comment != nil { + multiLine = true + } + if i < len(s)-1 { + b = append(b, ", "...) + } + } + // Force multi-lined output when printing a removed/inserted node that + // is sufficiently long. + if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > 80 { + multiLine = true + } + if !multiLine { + return b, textLine(b[n0:]) + } + return b, s +} + +func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte { + alignKeyLens := s.alignLens( + func(r textRecord) bool { + _, isLine := r.Value.(textLine) + return r.Key == "" || !isLine + }, + func(r textRecord) int { return len(r.Key) }, + ) + alignValueLens := s.alignLens( + func(r textRecord) bool { + _, isLine := r.Value.(textLine) + return !isLine || r.Value.Equal(textEllipsis) || r.Comment == nil + }, + func(r textRecord) int { return len(r.Value.(textLine)) }, + ) + + // Format the list as a multi-lined output. + n++ + for i, r := range s { + b = n.appendIndent(append(b, '\n'), d|r.Diff) + if r.Key != "" { + b = append(b, r.Key+": "...) + } + b = alignKeyLens[i].appendChar(b, ' ') + + b = r.Value.formatExpandedTo(b, d|r.Diff, n) + if !r.Value.Equal(textEllipsis) { + b = append(b, ',') + } + b = alignValueLens[i].appendChar(b, ' ') + + if r.Comment != nil { + b = append(b, " // "+r.Comment.String()...) + } + } + n-- + + return n.appendIndent(append(b, '\n'), d) +} + +func (s textList) alignLens( + skipFunc func(textRecord) bool, + lenFunc func(textRecord) int, +) []repeatCount { + var startIdx, endIdx, maxLen int + lens := make([]repeatCount, len(s)) + for i, r := range s { + if skipFunc(r) { + for j := startIdx; j < endIdx && j < len(s); j++ { + lens[j] = repeatCount(maxLen - lenFunc(s[j])) + } + startIdx, endIdx, maxLen = i+1, i+1, 0 + } else { + if maxLen < lenFunc(r) { + maxLen = lenFunc(r) + } + endIdx = i + 1 + } + } + for j := startIdx; j < endIdx && j < len(s); j++ { + lens[j] = repeatCount(maxLen - lenFunc(s[j])) + } + return lens +} + +// textLine is a single-line segment of text and is always a leaf node +// in the textNode tree. +type textLine []byte + +var ( + textNil = textLine("nil") + textEllipsis = textLine("...") +) + +func (s textLine) Len() int { + return len(s) +} +func (s1 textLine) Equal(s2 textNode) bool { + if s2, ok := s2.(textLine); ok { + return bytes.Equal([]byte(s1), []byte(s2)) + } + return false +} +func (s textLine) String() string { + return string(s) +} +func (s textLine) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) { + return append(b, s...), s +} +func (s textLine) formatExpandedTo(b []byte, _ diffMode, _ indentMode) []byte { + return append(b, s...) +} + +type diffStats struct { + Name string + NumIgnored int + NumIdentical int + NumRemoved int + NumInserted int + NumModified int +} + +func (s diffStats) NumDiff() int { + return s.NumRemoved + s.NumInserted + s.NumModified +} + +func (s diffStats) Append(ds diffStats) diffStats { + assert(s.Name == ds.Name) + s.NumIgnored += ds.NumIgnored + s.NumIdentical += ds.NumIdentical + s.NumRemoved += ds.NumRemoved + s.NumInserted += ds.NumInserted + s.NumModified += ds.NumModified + return s +} + +// String prints a humanly-readable summary of coalesced records. +// +// Example: +// diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields" +func (s diffStats) String() string { + var ss []string + var sum int + labels := [...]string{"ignored", "identical", "removed", "inserted", "modified"} + counts := [...]int{s.NumIgnored, s.NumIdentical, s.NumRemoved, s.NumInserted, s.NumModified} + for i, n := range counts { + if n > 0 { + ss = append(ss, fmt.Sprintf("%d %v", n, labels[i])) + } + sum += n + } + + // Pluralize the name (adjusting for some obscure English grammar rules). + name := s.Name + if sum > 1 { + name = name + "s" + if strings.HasSuffix(name, "ys") { + name = name[:len(name)-2] + "ies" // e.g., "entrys" => "entries" + } + } + + // Format the list according to English grammar (with Oxford comma). + switch n := len(ss); n { + case 0: + return "" + case 1, 2: + return strings.Join(ss, " and ") + " " + name + default: + return strings.Join(ss[:n-1], ", ") + ", and " + ss[n-1] + " " + name + } +} + +type commentString string + +func (s commentString) String() string { return string(s) } diff --git a/vendor/github.com/google/go-cmp/cmp/report_value.go b/vendor/github.com/google/go-cmp/cmp/report_value.go new file mode 100644 index 000000000000..83031a7f5070 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/report_value.go @@ -0,0 +1,121 @@ +// Copyright 2019, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmp + +import "reflect" + +// valueNode represents a single node within a report, which is a +// structured representation of the value tree, containing information +// regarding which nodes are equal or not. +type valueNode struct { + parent *valueNode + + Type reflect.Type + ValueX reflect.Value + ValueY reflect.Value + + // NumSame is the number of leaf nodes that are equal. + // All descendants are equal only if NumDiff is 0. + NumSame int + // NumDiff is the number of leaf nodes that are not equal. + NumDiff int + // NumIgnored is the number of leaf nodes that are ignored. + NumIgnored int + // NumCompared is the number of leaf nodes that were compared + // using an Equal method or Comparer function. + NumCompared int + // NumTransformed is the number of non-leaf nodes that were transformed. + NumTransformed int + // NumChildren is the number of transitive descendants of this node. + // This counts from zero; thus, leaf nodes have no descendants. + NumChildren int + // MaxDepth is the maximum depth of the tree. This counts from zero; + // thus, leaf nodes have a depth of zero. + MaxDepth int + + // Records is a list of struct fields, slice elements, or map entries. + Records []reportRecord // If populated, implies Value is not populated + + // Value is the result of a transformation, pointer indirect, of + // type assertion. + Value *valueNode // If populated, implies Records is not populated + + // TransformerName is the name of the transformer. + TransformerName string // If non-empty, implies Value is populated +} +type reportRecord struct { + Key reflect.Value // Invalid for slice element + Value *valueNode +} + +func (parent *valueNode) PushStep(ps PathStep) (child *valueNode) { + vx, vy := ps.Values() + child = &valueNode{parent: parent, Type: ps.Type(), ValueX: vx, ValueY: vy} + switch s := ps.(type) { + case StructField: + assert(parent.Value == nil) + parent.Records = append(parent.Records, reportRecord{Key: reflect.ValueOf(s.Name()), Value: child}) + case SliceIndex: + assert(parent.Value == nil) + parent.Records = append(parent.Records, reportRecord{Value: child}) + case MapIndex: + assert(parent.Value == nil) + parent.Records = append(parent.Records, reportRecord{Key: s.Key(), Value: child}) + case Indirect: + assert(parent.Value == nil && parent.Records == nil) + parent.Value = child + case TypeAssertion: + assert(parent.Value == nil && parent.Records == nil) + parent.Value = child + case Transform: + assert(parent.Value == nil && parent.Records == nil) + parent.Value = child + parent.TransformerName = s.Name() + parent.NumTransformed++ + default: + assert(parent == nil) // Must be the root step + } + return child +} + +func (r *valueNode) Report(rs Result) { + assert(r.MaxDepth == 0) // May only be called on leaf nodes + + if rs.ByIgnore() { + r.NumIgnored++ + } else { + if rs.Equal() { + r.NumSame++ + } else { + r.NumDiff++ + } + } + assert(r.NumSame+r.NumDiff+r.NumIgnored == 1) + + if rs.ByMethod() { + r.NumCompared++ + } + if rs.ByFunc() { + r.NumCompared++ + } + assert(r.NumCompared <= 1) +} + +func (child *valueNode) PopStep() (parent *valueNode) { + if child.parent == nil { + return nil + } + parent = child.parent + parent.NumSame += child.NumSame + parent.NumDiff += child.NumDiff + parent.NumIgnored += child.NumIgnored + parent.NumCompared += child.NumCompared + parent.NumTransformed += child.NumTransformed + parent.NumChildren += child.NumChildren + 1 + if parent.MaxDepth < child.MaxDepth+1 { + parent.MaxDepth = child.MaxDepth + 1 + } + return parent +} diff --git a/vendor/github.com/google/go-cmp/cmp/reporter.go b/vendor/github.com/google/go-cmp/cmp/reporter.go deleted file mode 100644 index 20e9f18e0dd5..000000000000 --- a/vendor/github.com/google/go-cmp/cmp/reporter.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE.md file. - -package cmp - -import ( - "fmt" - "reflect" - "strings" - - "github.com/google/go-cmp/cmp/internal/value" -) - -type defaultReporter struct { - Option - diffs []string // List of differences, possibly truncated - ndiffs int // Total number of differences - nbytes int // Number of bytes in diffs - nlines int // Number of lines in diffs -} - -var _ reporter = (*defaultReporter)(nil) - -func (r *defaultReporter) Report(x, y reflect.Value, eq bool, p Path) { - if eq { - return // Ignore equal results - } - const maxBytes = 4096 - const maxLines = 256 - r.ndiffs++ - if r.nbytes < maxBytes && r.nlines < maxLines { - sx := value.Format(x, value.FormatConfig{UseStringer: true}) - sy := value.Format(y, value.FormatConfig{UseStringer: true}) - if sx == sy { - // Unhelpful output, so use more exact formatting. - sx = value.Format(x, value.FormatConfig{PrintPrimitiveType: true}) - sy = value.Format(y, value.FormatConfig{PrintPrimitiveType: true}) - } - s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy) - r.diffs = append(r.diffs, s) - r.nbytes += len(s) - r.nlines += strings.Count(s, "\n") - } -} - -func (r *defaultReporter) String() string { - s := strings.Join(r.diffs, "") - if r.ndiffs == len(r.diffs) { - return s - } - return fmt.Sprintf("%s... %d more differences ...", s, r.ndiffs-len(r.diffs)) -} diff --git a/vendor/github.com/lukechampine/freeze/LICENSE b/vendor/github.com/lukechampine/freeze/LICENSE deleted file mode 100644 index 79479d437d93..000000000000 --- a/vendor/github.com/lukechampine/freeze/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Luke Champine - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/lukechampine/freeze/README.md b/vendor/github.com/lukechampine/freeze/README.md deleted file mode 100644 index 04fe9912adf1..000000000000 --- a/vendor/github.com/lukechampine/freeze/README.md +++ /dev/null @@ -1,112 +0,0 @@ -Freeze -====== - -[![GoDoc](https://godoc.org/github.com/lukechampine/freeze?status.svg)](https://godoc.org/github.com/lukechampine/freeze) -[![Go Report Card](http://goreportcard.com/badge/github.com/lukechampine/freeze)](https://goreportcard.com/report/github.com/lukechampine/freeze) - -``` -go get github.com/lukechampine/freeze -``` - -Package freeze enables the "freezing" of data, similar to JavaScript's -`Object.freeze()`. A frozen object cannot be modified; attempting to do so -will result in an unrecoverable panic. - -Freezing is useful for providing soft guarantees of immutability. That is: the -compiler can't prevent you from mutating an frozen object, but the runtime -can. One of the unfortunate aspects of Go is its limited support for -constants: structs, slices, and even arrays cannot be declared as consts. This -becomes a problem when you want to pass a slice around to many consumers -without worrying about them modifying it. With freeze, you can guard against -these unwanted or intended behaviors. - -To accomplish this, the `mprotect` syscall is used. Sadly, this necessitates -allocating new memory via `mmap` and copying the data into it. This -performance penalty should not be prohibitive, but it's something to be aware -of. - -In case it wasn't clear from the previous paragraph, this package is not -intended to be used in production. A well-designed API is a much saner solution -than freezing your data structures. I would even caution against using `freeze` -in your automated testing, due to its platform-specific nature. `freeze` is -best used for "one-off" debugging. Something like this: - -1. Observe bug -2. Suspect that shared mutable data is the culprit -3. Call `freeze.Object` on the data after it is created -4. Run program again; it crashes -5. Inspect stack trace to identify where the data was modified -6. Fix bug -7. Remove call to `freeze.Object` - -Again: **do not use `freeze` in production.** It's a cool proof-of-concept, and -it can be useful for debugging, but that's about it. Let me put it another way: -`freeze` imports four packages: `reflect`, `runtime`, `unsafe`, and `syscall` -(actually `golang.org/x/sys/unix`). Does that sound like a package you want to -depend on? - -Okay, back to the real documention: - -Functions are provided for freezing the three "pointer types:" `Pointer`, -`Slice`, and `Map`. Each function returns a copy of their input that is backed -by protected memory. In addition, `Object` is provided for freezing -recursively. Given a slice of pointers, `Object` will prevent modifications to -both the pointer data and the slice data, while `Slice` merely does the -latter. - -To freeze an object: - -```go -type foo struct { - X int - y bool // yes, freeze works on unexported fields! -} -f := &foo{3, true} -f = freeze.Object(f).(*foo) -println(f.X) // ok; prints 3 -f.X++ // not ok; panics -``` - -Note that since `foo` does not contain any pointers, calling `Pointer(f)` -would have the same effect here. - -It is recommended that, where convenient, you reassign the return value to its -original variable, as with append. Otherwise, you will retain both the mutable -original and the frozen copy. - -Likewise, to freeze a slice: - -```go -xs := []int{1, 2, 3} -xs = freeze.Slice(xs).([]int) -println(xs[0]) // ok; prints 1 -xs[0]++ // not ok; panics -``` - -Interfaces can also be frozen, since internally they are just pointers to -objects. The effect of this is that the interface's pure methods can still be -called, but impure methods cannot. Unfortunately, the impurity of a given -method is defined by the implementation, not the interface. Even a `String()` -method could conceivably modify some internal state. Furthermore, the caveat -about unexported struct fields (see below) applies here, so many exported -objects cannot be completely frozen. - -## Caveats ## - -This package depends heavily on the internal representations of the `slice` -and `map` types. These objects are not likely to change, but if they do, this -package will break. - -In general, you can't call `Object` on the same object twice. This is because -`Object` will attempt to rewrite the object's internal pointers -- which is a -memory modification. Calling `Pointer` or `Slice` twice should be fine. - -`Object` cannot descend into unexported struct fields. It can still freeze the -field itself, but if the field contains a pointer, the data it points to will -not be frozen. - -Appending to a frozen slice will trigger a panic iff `len(slice) < cap(slice)`. -This is because appending to a full slice will allocate new memory. - -Unix is the only supported platform. Windows support is not planned, because -it doesn't support a syscall analogous to `mprotect`. diff --git a/vendor/github.com/lukechampine/freeze/freeze.go b/vendor/github.com/lukechampine/freeze/freeze.go deleted file mode 100644 index dae398870339..000000000000 --- a/vendor/github.com/lukechampine/freeze/freeze.go +++ /dev/null @@ -1,290 +0,0 @@ -/* -Package freeze enables the "freezing" of data, similar to JavaScript's -Object.freeze(). A frozen object cannot be modified; attempting to do so will -result in an unrecoverable panic. - -Freezing is useful for providing soft guarantees of immutability. That is: the -compiler can't prevent you from mutating an frozen object, but the runtime -can. One of the unfortunate aspects of Go is its limited support for -constants: structs, slices, and even arrays cannot be declared as consts. This -becomes a problem when you want to pass a slice around to many consumers -without worrying about them modifying it. With freeze, you can guard against -these unwanted or intended behaviors. - -To accomplish this, the mprotect syscall is used. Sadly, this necessitates -allocating new memory via mmap and copying the data into it. This performance -penalty should not be prohibitive, but it's something to be aware of. - -In case it wasn't clear from the previous paragraph, this package is not -intended to be used in production. A well-designed API is a much saner -solution than freezing your data structures. I would even caution against -using freeze in your automated testing, due to its platform-specific nature. -freeze is best used for "one-off" debugging. Something like this: - -1. Observe bug -2. Suspect that shared mutable data is the culprit -3. Call freeze.Object on the data after it is created -4. Run program again; it crashes -5. Inspect stack trace to identify where the data was modified -6. Fix bug -7. Remove call to freeze.Object - -Again: do not use freeze in production. It's a cool proof-of-concept, and it -can be useful for debugging, but that's about it. Let me put it another way: -freeze imports four packages: reflect, runtime, unsafe, and syscall (actually -golang.org/x/sys/unix). Does that sound like a package you want to depend on? - -Okay, back to the real documention: - -Functions are provided for freezing the three "pointer types:" Pointer, Slice, -and Map. Each function returns a copy of their input that is backed by -protected memory. In addition, Object is provided for freezing recursively. -Given a slice of pointers, Object will prevent modifications to both the -pointer data and the slice data, while Slice merely does the latter. - -To freeze an object: - - type foo struct { - X int - y bool // yes, freeze works on unexported fields! - } - f := &foo{3, true} - f = freeze.Object(f).(*foo) - println(f.X) // ok; prints 3 - f.X++ // not ok; panics - -Note that since foo does not contain any pointers, calling Pointer(f) would -have the same effect here. - -It is recommended that, where convenient, you reassign the return value to its -original variable, as with append. Otherwise, you will retain both the mutable -original and the frozen copy. - -Likewise, to freeze a slice: - - xs := []int{1, 2, 3} - xs = freeze.Slice(xs).([]int) - println(xs[0]) // ok; prints 1 - xs[0]++ // not ok; panics - -Interfaces can also be frozen, since internally they are just pointers to -objects. The effect of this is that the interface's pure methods can still be -called, but impure methods cannot. Unfortunately the impurity of a given -method is defined by the implementation, not the interface. Even a String -method could conceivably modify some internal state. Furthermore, the caveat -about unexported struct fields (see below) applies here, so many exported -objects cannot be completely frozen. - -Caveats - -This package depends heavily on the internal representations of the slice and -map types. These objects are not likely to change, but if they do, this -package will break. - -In general, you can't call Object on the same object twice. This is because -Object will attempt to rewrite the object's internal pointers -- which is a -memory modification. Calling Pointer or Slice twice should be fine. - -Object cannot descend into unexported struct fields. It can still freeze the -field itself, but if the field contains a pointer, the data it points to will -not be frozen. - -Appending to a frozen slice will trigger a panic iff len(slice) < cap(slice). -This is because appending to a full slice will allocate new memory. - -Unix is the only supported platform. Windows support is not planned, because -it doesn't support a syscall analogous to mprotect. -*/ -package freeze - -import ( - "reflect" - "runtime" - "unsafe" - - "golang.org/x/sys/unix" -) - -// Pointer returns a frozen copy of v, which must be a pointer. Future writes -// to the copy's memory will result in a panic. In most cases, the copy should -// be reassigned to v. -func Pointer(v interface{}) interface{} { - if v == nil { - return v - } - typ := reflect.TypeOf(v) - if typ.Kind() != reflect.Ptr { - panic("Pointer called on non-pointer type") - } - - // freeze the memory pointed to by the interface's data pointer - size := typ.Elem().Size() - ptrs := (*[2]uintptr)(unsafe.Pointer(&v)) - ptrs[1] = copyAndFreeze(ptrs[1], size) - - return v -} - -// Slice returns a frozen copy of v, which must be a slice. Future writes to -// the copy's memory will result in a panic. In most cases, the copy should be -// reassigned to v. -func Slice(v interface{}) interface{} { - if v == nil { - return v - } - val := reflect.ValueOf(v) - if val.Kind() != reflect.Slice { - panic("Slice called on non-slice type") - } - - // freeze the memory pointed to by the slice's data pointer - size := val.Type().Elem().Size() * uintptr(val.Len()) - slice := (*reflect.SliceHeader)((*[2]unsafe.Pointer)(unsafe.Pointer(&v))[1]) - slice.Data = copyAndFreeze(slice.Data, size) - - return v -} - -// Map returns a frozen copy of v, which must be a map. Future writes to -// the copy's memory will result in a panic. In most cases, the copy should be -// reassigned to v. Note that both the keys and values of the map are frozen. -func Map(v interface{}) interface{} { - if v == nil { - return v - } - typ := reflect.TypeOf(v) - if typ.Kind() != reflect.Map { - panic("Map called on non-map type") - } - - // copied from runtime/hmap.go - type hmap struct { - count int - flags uint8 - B uint8 - hash0 uint32 - buckets uintptr - oldbuckets uintptr - nevacuate uintptr - overflow *[2]*[]uintptr - } - - // convert v to a hmap so we can access 'B' and 'buckets' - m := (*hmap)((*[2]unsafe.Pointer)(unsafe.Pointer(&v))[1]) - - // copied from reflect/type.go - bucketSize := 8*(1+typ.Key().Size()+typ.Elem().Size()) + unsafe.Sizeof(uintptr(0)) - // size of map's bucket data is 2^B * bucketSize - size := (uintptr(1) << m.B) * bucketSize - - // freeze the map's buckets - m.buckets = copyAndFreeze(m.buckets, size) - - return v -} - -// Object returns a recursively frozen copy of v, which must be a pointer, -// slice, or map. It will descend into pointers, arrays, slices, and structs -// until "bottoming out," freezing the entire chain. Passing a cyclic -// structure to Object will result in infinite recursion. Note that Object can -// only descend into exported struct fields (the fields themselves will still -// be frozen). -func Object(v interface{}) interface{} { - if v == nil { - return v - } - val := reflect.ValueOf(v) - switch val.Kind() { - case reflect.Ptr, reflect.Slice, reflect.Map: - return object(val).Interface() - } - panic("Object called on invalid type") -} - -// object updates all pointers in val to point to frozen memory containing the -// same data. -func object(val reflect.Value) reflect.Value { - // we only need to recurse into types that might have pointers - hasPtrs := func(t reflect.Type) bool { - switch t.Kind() { - case reflect.Ptr, reflect.Array, reflect.Slice, reflect.Map, reflect.Struct: - return true - } - return false - } - - switch val.Type().Kind() { - default: - return val - - case reflect.Ptr: - if val.IsNil() { - return val - } else if hasPtrs(val.Type().Elem()) { - val.Elem().Set(object(val.Elem())) - } - return reflect.ValueOf(Pointer(val.Interface())) - - case reflect.Array: - if hasPtrs(val.Type().Elem()) { - for i := 0; i < val.Len(); i++ { - val.Index(i).Set(object(val.Index(i))) - } - } - return val - - case reflect.Slice: - if hasPtrs(val.Type().Elem()) { - for i := 0; i < val.Len(); i++ { - val.Index(i).Set(object(val.Index(i))) - } - } - return reflect.ValueOf(Slice(val.Interface())) - - case reflect.Map: - if hasPtrs(val.Type().Elem()) || hasPtrs(val.Type().Key()) { - newMap := reflect.MakeMap(val.Type()) - for _, key := range val.MapKeys() { - newMap.SetMapIndex(object(key), object(val.MapIndex(key))) - } - val = newMap - } - return reflect.ValueOf(Map(val.Interface())) - - case reflect.Struct: - for i := 0; i < val.NumField(); i++ { - // can't recurse into unexported fields - t := val.Type().Field(i) - if !(t.PkgPath != "" && !t.Anonymous) && hasPtrs(t.Type) { - val.Field(i).Set(object(val.Field(i))) - } - } - return val - } -} - -// copyAndFreeze copies n bytes from dataptr into new memory, freezes it, and -// returns a uintptr to the new memory. -func copyAndFreeze(dataptr, n uintptr) uintptr { - if dataptr == 0 || n == 0 { - return dataptr - } - // allocate new memory to be frozen - newMem, err := unix.Mmap(-1, 0, int(n), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_ANON|unix.MAP_PRIVATE) - if err != nil { - panic(err) - } - // set a finalizer to unmap the memory when it would normally be GC'd - runtime.SetFinalizer(&newMem, func(b *[]byte) { _ = unix.Munmap(*b) }) - - // copy n bytes into newMem - copy(newMem, *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{dataptr, int(n), int(n)}))) - - // freeze the new memory - if err = unix.Mprotect(newMem, unix.PROT_READ); err != nil { - panic(err) - } - - // return pointer to new memory - return uintptr(unsafe.Pointer(&newMem[0])) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 224f6ff29a31..c13a2a6347af 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -148,8 +148,8 @@ github.com/envoyproxy/go-control-plane/envoy/config/filter/network/rbac/v2 github.com/envoyproxy/go-control-plane/pkg/util github.com/envoyproxy/go-control-plane/envoy/api/v2/core github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint -github.com/envoyproxy/go-control-plane/envoy/api/v2/route github.com/envoyproxy/go-control-plane/envoy/api/v2/cluster +github.com/envoyproxy/go-control-plane/envoy/api/v2/route github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v2 github.com/envoyproxy/go-control-plane/envoy/config/filter/accesslog/v2 github.com/envoyproxy/go-control-plane/envoy/config/filter/network/mongo_proxy/v2 @@ -210,7 +210,7 @@ github.com/gobwas/glob/util/strings # github.com/gogo/googleapis v1.1.0 github.com/gogo/googleapis/google/rpc github.com/gogo/googleapis/google/api -# github.com/gogo/protobuf v1.2.1 +# github.com/gogo/protobuf v1.2.1 => github.com/istio/gogo-protobuf v1.2.2-0.20190726125433-4c9abdb3090c github.com/gogo/protobuf/jsonpb github.com/gogo/protobuf/types github.com/gogo/protobuf/proto @@ -257,10 +257,11 @@ github.com/google/cel-go/common/packages github.com/google/cel-go/common/overloads github.com/google/cel-go/common/types/pb github.com/google/cel-go/parser/gen -# github.com/google/go-cmp v0.2.0 +# github.com/google/go-cmp v0.3.0 github.com/google/go-cmp/cmp github.com/google/go-cmp/cmp/cmpopts github.com/google/go-cmp/cmp/internal/diff +github.com/google/go-cmp/cmp/internal/flags github.com/google/go-cmp/cmp/internal/function github.com/google/go-cmp/cmp/internal/value # github.com/google/go-github v15.0.0+incompatible @@ -356,8 +357,6 @@ github.com/lestrrat-go/jwx/internal/base64 github.com/lestrrat-go/jwx/internal/option github.com/lestrrat-go/jwx/jws/sign github.com/lestrrat-go/jwx/jws/verify -# github.com/lukechampine/freeze v0.0.0-20160818180733-f514e08ae5a0 -github.com/lukechampine/freeze # github.com/magiconair/properties v1.8.0 github.com/magiconair/properties # github.com/matttproud/golang_protobuf_extensions v1.0.1