From 274d221c781a585ce167ff24d43bc1533ceb2654 Mon Sep 17 00:00:00 2001 From: Alice Wasko Date: Thu, 13 Apr 2023 15:12:27 -0700 Subject: [PATCH 01/47] 0.4.0-rc.1: Pin Envoy Proxy and Envoy Ratelimit for release (#1305) 0.4.0-rc.1: pin envoy and ratelimit Signed-off-by: AliceProxy --- api/config/v1alpha1/shared_types.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/config/v1alpha1/shared_types.go b/api/config/v1alpha1/shared_types.go index c45eca1fb86..40872444053 100644 --- a/api/config/v1alpha1/shared_types.go +++ b/api/config/v1alpha1/shared_types.go @@ -15,9 +15,9 @@ const ( // DefaultDeploymentMemoryResourceRequests for deployment memory resource DefaultDeploymentMemoryResourceRequests = "512Mi" // DefaultEnvoyProxyImage is the default image used by envoyproxy - DefaultEnvoyProxyImage = "envoyproxy/envoy-dev:latest" + DefaultEnvoyProxyImage = "envoyproxy/envoy:v1.25-latest" // DefaultRateLimitImage is the default image used by ratelimit. - DefaultRateLimitImage = "envoyproxy/ratelimit:master" + DefaultRateLimitImage = "envoyproxy/ratelimit:542a6047" ) // GroupVersionKind unambiguously identifies a Kind. From 36839fd69f44a428fc4a153a4ae825340af1b580 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Tue, 18 Apr 2023 23:15:31 -0700 Subject: [PATCH 02/47] [release/v0.4] chore: bump envoy proxy to v1.26 (#1327) https://github.com/envoyproxy/envoy/releases/tag/v1.26.0 Signed-off-by: Arko Dasgupta --- api/config/v1alpha1/shared_types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/config/v1alpha1/shared_types.go b/api/config/v1alpha1/shared_types.go index 40872444053..e270d2acf5d 100644 --- a/api/config/v1alpha1/shared_types.go +++ b/api/config/v1alpha1/shared_types.go @@ -15,7 +15,7 @@ const ( // DefaultDeploymentMemoryResourceRequests for deployment memory resource DefaultDeploymentMemoryResourceRequests = "512Mi" // DefaultEnvoyProxyImage is the default image used by envoyproxy - DefaultEnvoyProxyImage = "envoyproxy/envoy:v1.25-latest" + DefaultEnvoyProxyImage = "envoyproxy/envoy:v1.26-latest" // DefaultRateLimitImage is the default image used by ratelimit. DefaultRateLimitImage = "envoyproxy/ratelimit:542a6047" ) From c92514fae876b3f3285c52ece65d20d55ae2e8c9 Mon Sep 17 00:00:00 2001 From: Alice Wasko Date: Mon, 24 Apr 2023 12:13:21 -0700 Subject: [PATCH 03/47] Release - 0.4.0: Cherry Pick Fixes (#1350) * Extension: fix pointer error (#1323) (cherry picked from commit 2bf96076c697bbda2c6de6abb55dd175676fe81d) Signed-off-by: AliceProxy * fix: add the namespace resource within helm templates (#1332) Add the namespace resource within helm templates This is unfortunate workaround due the difference in UX between `helm template` and `helm install` The project recommends `helm install` as a way to install EG which supports a `--create-namespace` flag to create a namespace However we also generate a static YAML using `helm template` as part of the release artficat so a user can install the YAML directly using `kubectl` instead of `helm` . The issue here is `helm template` does not support `--create-namespace`, so instead this commit adds a knob called `createNamespace` to the Helm chart which is `false` by default, but turned on during `make generate-manifests` Fixes: https://github.com/envoyproxy/gateway/issues/1307 Signed-off-by: Arko Dasgupta (cherry picked from commit 9d6d699e83b37f98f2eca554d68290234ae231c9) Signed-off-by: AliceProxy --------- Signed-off-by: Arko Dasgupta Signed-off-by: AliceProxy Co-authored-by: Arko Dasgupta --- .../gateway-helm/templates/certgen-rbac.yaml | 3 ++ charts/gateway-helm/templates/certgen.yaml | 1 + .../templates/envoy-gateway-config.yaml | 1 + .../templates/envoy-gateway-deployment.yaml | 2 + .../envoy-gateway-metrics-service.yaml | 1 + .../templates/envoy-gateway-service.yaml | 1 + .../templates/infra-manager-rbac.yaml | 2 + .../templates/leader-election-rbac.yaml | 2 + .../templates/metrics-reader-rbac.yaml | 1 + charts/gateway-helm/templates/namespace.yaml | 6 +++ charts/gateway-helm/values.tmpl.yaml | 1 + internal/extension/testutils/hooks.go | 31 ++++++++++---- internal/xds/translator/extension.go | 42 +++++++++++++++---- tools/make/kube.mk | 2 +- 14 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 charts/gateway-helm/templates/namespace.yaml diff --git a/charts/gateway-helm/templates/certgen-rbac.yaml b/charts/gateway-helm/templates/certgen-rbac.yaml index f78c36709b6..ff805dad3db 100644 --- a/charts/gateway-helm/templates/certgen-rbac.yaml +++ b/charts/gateway-helm/templates/certgen-rbac.yaml @@ -2,6 +2,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: {{ include "eg.fullname" . }}-certgen + namespace: '{{ .Release.Namespace }}' labels: {{- include "eg.labels" . | nindent 4 }} annotations: @@ -11,6 +12,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: {{ include "eg.fullname" . }}-certgen + namespace: '{{ .Release.Namespace }}' labels: {{- include "eg.labels" . | nindent 4 }} annotations: @@ -29,6 +31,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: {{ include "eg.fullname" . }}-certgen + namespace: '{{ .Release.Namespace }}' labels: {{- include "eg.labels" . | nindent 4 }} annotations: diff --git a/charts/gateway-helm/templates/certgen.yaml b/charts/gateway-helm/templates/certgen.yaml index 2b40f599eeb..25f65196da6 100644 --- a/charts/gateway-helm/templates/certgen.yaml +++ b/charts/gateway-helm/templates/certgen.yaml @@ -2,6 +2,7 @@ apiVersion: batch/v1 kind: Job metadata: name: {{ include "eg.fullname" . }}-certgen + namespace: '{{ .Release.Namespace }}' labels: {{- include "eg.labels" . | nindent 4 }} annotations: diff --git a/charts/gateway-helm/templates/envoy-gateway-config.yaml b/charts/gateway-helm/templates/envoy-gateway-config.yaml index 255030c9ee7..c969f60454f 100644 --- a/charts/gateway-helm/templates/envoy-gateway-config.yaml +++ b/charts/gateway-helm/templates/envoy-gateway-config.yaml @@ -2,6 +2,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: envoy-gateway-config + namespace: '{{ .Release.Namespace }}' labels: {{- include "eg.labels" . | nindent 4 }} data: diff --git a/charts/gateway-helm/templates/envoy-gateway-deployment.yaml b/charts/gateway-helm/templates/envoy-gateway-deployment.yaml index bc4c6224845..e2cc40b9a24 100644 --- a/charts/gateway-helm/templates/envoy-gateway-deployment.yaml +++ b/charts/gateway-helm/templates/envoy-gateway-deployment.yaml @@ -2,6 +2,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: envoy-gateway + namespace: '{{ .Release.Namespace }}' labels: {{- include "eg.labels" . | nindent 4 }} --- @@ -9,6 +10,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: envoy-gateway + namespace: '{{ .Release.Namespace }}' labels: control-plane: envoy-gateway {{- include "eg.labels" . | nindent 4 }} diff --git a/charts/gateway-helm/templates/envoy-gateway-metrics-service.yaml b/charts/gateway-helm/templates/envoy-gateway-metrics-service.yaml index b19069eec0c..bd5f1c6b8e2 100644 --- a/charts/gateway-helm/templates/envoy-gateway-metrics-service.yaml +++ b/charts/gateway-helm/templates/envoy-gateway-metrics-service.yaml @@ -2,6 +2,7 @@ apiVersion: v1 kind: Service metadata: name: envoy-gateway-metrics-service + namespace: '{{ .Release.Namespace }}' labels: control-plane: envoy-gateway {{- include "eg.labels" . | nindent 4 }} diff --git a/charts/gateway-helm/templates/envoy-gateway-service.yaml b/charts/gateway-helm/templates/envoy-gateway-service.yaml index 1b1a0c283a4..b9dd4cd5f22 100644 --- a/charts/gateway-helm/templates/envoy-gateway-service.yaml +++ b/charts/gateway-helm/templates/envoy-gateway-service.yaml @@ -2,6 +2,7 @@ apiVersion: v1 kind: Service metadata: name: envoy-gateway + namespace: '{{ .Release.Namespace }}' labels: control-plane: envoy-gateway {{- include "eg.labels" . | nindent 4 }} diff --git a/charts/gateway-helm/templates/infra-manager-rbac.yaml b/charts/gateway-helm/templates/infra-manager-rbac.yaml index 95b8669bc31..6f3e5a4677f 100644 --- a/charts/gateway-helm/templates/infra-manager-rbac.yaml +++ b/charts/gateway-helm/templates/infra-manager-rbac.yaml @@ -2,6 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: {{ include "eg.fullname" . }}-infra-manager + namespace: '{{ .Release.Namespace }}' labels: {{- include "eg.labels" . | nindent 4 }} rules: @@ -29,6 +30,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: {{ include "eg.fullname" . }}-infra-manager + namespace: '{{ .Release.Namespace }}' labels: {{- include "eg.labels" . | nindent 4 }} roleRef: diff --git a/charts/gateway-helm/templates/leader-election-rbac.yaml b/charts/gateway-helm/templates/leader-election-rbac.yaml index ffd849f4272..5b59f34c7ca 100644 --- a/charts/gateway-helm/templates/leader-election-rbac.yaml +++ b/charts/gateway-helm/templates/leader-election-rbac.yaml @@ -2,6 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: {{ include "eg.fullname" . }}-leader-election-role + namespace: '{{ .Release.Namespace }}' labels: {{- include "eg.labels" . | nindent 4 }} rules: @@ -41,6 +42,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: {{ include "eg.fullname" . }}-leader-election-rolebinding + namespace: '{{ .Release.Namespace }}' labels: {{- include "eg.labels" . | nindent 4 }} roleRef: diff --git a/charts/gateway-helm/templates/metrics-reader-rbac.yaml b/charts/gateway-helm/templates/metrics-reader-rbac.yaml index b3bec93b99b..3b77e714185 100644 --- a/charts/gateway-helm/templates/metrics-reader-rbac.yaml +++ b/charts/gateway-helm/templates/metrics-reader-rbac.yaml @@ -2,6 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: {{ include "eg.fullname" . }}-metrics-reader + namespace: '{{ .Release.Namespace }}' labels: {{- include "eg.labels" . | nindent 4 }} rules: diff --git a/charts/gateway-helm/templates/namespace.yaml b/charts/gateway-helm/templates/namespace.yaml new file mode 100644 index 00000000000..0361b229daa --- /dev/null +++ b/charts/gateway-helm/templates/namespace.yaml @@ -0,0 +1,6 @@ +{{ if .Values.createNamespace }} +apiVersion: v1 +kind: Namespace +metadata: + name: '{{ .Release.Namespace }}' +{{ end }} diff --git a/charts/gateway-helm/values.tmpl.yaml b/charts/gateway-helm/values.tmpl.yaml index 94bbd583d27..d1fdd1979d9 100644 --- a/charts/gateway-helm/values.tmpl.yaml +++ b/charts/gateway-helm/values.tmpl.yaml @@ -42,3 +42,4 @@ envoyGatewayMetricsService: protocol: TCP targetPort: https +createNamespace: false diff --git a/internal/extension/testutils/hooks.go b/internal/extension/testutils/hooks.go index 3905923de79..d42867163ba 100644 --- a/internal/extension/testutils/hooks.go +++ b/internal/extension/testutils/hooks.go @@ -16,6 +16,7 @@ import ( listenerV3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" routeV3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" tlsV3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + "github.com/golang/protobuf/proto" "google.golang.org/protobuf/types/known/durationpb" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -24,16 +25,20 @@ type XDSHookClient struct{} // PostRouteModifyHook returns a modified version of the route using context info and the passed in extensionResources func (c *XDSHookClient) PostRouteModifyHook(route *routeV3.Route, routeHostnames []string, extensionResources []*unstructured.Unstructured) (*routeV3.Route, error) { + // Simulate an error an extension may return + if route.Name == "extension-post-xdsroute-hook-error" { + return nil, errors.New("route hook resource error") + } + + // Setup a new route to avoid operating directly on the passed in pointer for better test coverage that the + // route we are returning gets used properly + modifiedRoute := proto.Clone(route).(*routeV3.Route) for _, extensionResource := range extensionResources { - // Simulate an error an extension may return - if route.Name == "extension-post-xdsroute-hook-error" { - return nil, errors.New("route hook resource error") - } - route.ResponseHeadersToAdd = append(route.ResponseHeadersToAdd, + modifiedRoute.ResponseHeadersToAdd = append(modifiedRoute.ResponseHeadersToAdd, &coreV3.HeaderValueOption{ Header: &coreV3.HeaderValue{ Key: "mock-extension-was-here-route-name", - Value: route.Name, + Value: modifiedRoute.Name, }, }, &coreV3.HeaderValueOption{ @@ -68,7 +73,7 @@ func (c *XDSHookClient) PostRouteModifyHook(route *routeV3.Route, routeHostnames }, ) } - return route, nil + return modifiedRoute, nil } // PostVirtualHostModifyHook returns a modified version of the virtualhost with a new route injected @@ -78,7 +83,10 @@ func (c *XDSHookClient) PostVirtualHostModifyHook(vh *routeV3.VirtualHost) (*rou if vh.Name == "extension-post-xdsvirtualhost-hook-error" { return nil, fmt.Errorf("extension post xds virtual host hook error") } else if vh.Name == "extension-listener" { - vh.Routes = append(vh.Routes, &routeV3.Route{ + // Setup a new VirtualHost to avoid operating directly on the passed in pointer for better test coverage that the + // VirtualHost we are returning gets used properly + modifiedVH := proto.Clone(vh).(*routeV3.VirtualHost) + modifiedVH.Routes = append(modifiedVH.Routes, &routeV3.Route{ Name: "mock-extension-inserted-route", Action: &routeV3.Route_DirectResponse{ DirectResponse: &routeV3.DirectResponseAction{ @@ -86,6 +94,7 @@ func (c *XDSHookClient) PostVirtualHostModifyHook(vh *routeV3.VirtualHost) (*rou }, }, }) + return modifiedVH, nil } return vh, nil } @@ -100,7 +109,11 @@ func (c *XDSHookClient) PostHTTPListenerModifyHook(l *listenerV3.Listener) (*lis if l.Name == "extension-post-xdslistener-hook-error" { return nil, fmt.Errorf("extension post xds listener hook error") } else if l.Name == "extension-listener" { - l.StatPrefix = "mock-extension-inserted-prefix" + // Setup a new Listener to avoid operating directly on the passed in pointer for better test coverage that the + // Listener we are returning gets used properly + modifiedListener := proto.Clone(l).(*listenerV3.Listener) + modifiedListener.StatPrefix = "mock-extension-inserted-prefix" + return modifiedListener, nil } return l, nil } diff --git a/internal/xds/translator/extension.go b/internal/xds/translator/extension.go index c1248c7129d..0c925a75149 100644 --- a/internal/xds/translator/extension.go +++ b/internal/xds/translator/extension.go @@ -9,6 +9,10 @@ package translator import ( + "errors" + "fmt" + "reflect" + clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" @@ -17,11 +21,10 @@ import ( resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "github.com/envoyproxy/gateway/internal/ir" - "github.com/envoyproxy/gateway/internal/xds/types" - "github.com/envoyproxy/gateway/api/config/v1alpha1" extensionTypes "github.com/envoyproxy/gateway/internal/extension/types" + "github.com/envoyproxy/gateway/internal/ir" + "github.com/envoyproxy/gateway/internal/xds/types" ) func processExtensionPostRouteHook(route *routev3.Route, vHost *routev3.VirtualHost, irRoute *ir.HTTPRoute, em *extensionTypes.Manager) error { @@ -53,9 +56,9 @@ func processExtensionPostRouteHook(route *routev3.Route, vHost *routev3.VirtualH // If the extension returned a modified Route, then copy its to the one that was passed in as a reference if modifiedRoute != nil { - // Overwrite the pointer for the original route. - // Uses nolint because of the ineffectual assignment check - route = modifiedRoute //nolint + if err = deepCopyPtr(modifiedRoute, route); err != nil { + return err + } } return nil } @@ -81,9 +84,9 @@ func processExtensionPostVHostHook(vHost *routev3.VirtualHost, em *extensionType // If the extension returned a modified Virtual Host, then copy its to the one that was passed in as a reference if modifiedVH != nil { - // Overwrite the pointer for the original virtual host. - // Uses nolint because of the ineffectual assignment check - vHost = modifiedVH //nolint + if err = deepCopyPtr(modifiedVH, vHost); err != nil { + return err + } } return nil @@ -168,3 +171,24 @@ func processExtensionPostTranslationHook(tCtx *types.ResourceVersionTable, em *e return nil } + +func deepCopyPtr(src interface{}, dest interface{}) error { + if src == nil || dest == nil { + return errors.New("cannot deep copy nil pointer") + } + srcVal := reflect.ValueOf(src) + destVal := reflect.ValueOf(src) + if srcVal.Kind() == reflect.Ptr && destVal.Kind() == reflect.Ptr { + srcElem := srcVal.Elem() + destVal = reflect.New(srcElem.Type()) + destElem := destVal.Elem() + destElem.Set(srcElem) + reflect.ValueOf(dest).Elem().Set(destVal.Elem()) + } else { + return fmt.Errorf("cannot deep copy pointers to different kinds src %v != dest %v", + srcVal.Kind(), + destVal.Kind(), + ) + } + return nil +} diff --git a/tools/make/kube.mk b/tools/make/kube.mk index 77e1e9e7026..759cb596d34 100644 --- a/tools/make/kube.mk +++ b/tools/make/kube.mk @@ -125,7 +125,7 @@ generate-manifests: helm-generate ## Generate Kubernetes release manifests. @$(LOG_TARGET) @$(call log, "Generating kubernetes manifests") mkdir -p $(OUTPUT_DIR)/ - helm template eg charts/gateway-helm --include-crds --set deployment.envoyGateway.imagePullPolicy=$(IMAGE_PULL_POLICY) > $(OUTPUT_DIR)/install.yaml + helm template --set createNamespace=true eg charts/gateway-helm --include-crds --set deployment.envoyGateway.imagePullPolicy=$(IMAGE_PULL_POLICY) --namespace envoy-gateway-system > $(OUTPUT_DIR)/install.yaml @$(call log, "Added: $(OUTPUT_DIR)/install.yaml") cp examples/kubernetes/quickstart.yaml $(OUTPUT_DIR)/quickstart.yaml @$(call log, "Added: $(OUTPUT_DIR)/quickstart.yaml") From c4eb8da7aa8f809224d06e933897f5e9d6fb718e Mon Sep 17 00:00:00 2001 From: Alice Wasko Date: Mon, 24 Apr 2023 13:11:00 -0700 Subject: [PATCH 04/47] Cherry-pick 0.4.0 release notes to release/v0.4.0 (#1351) Release - 0.4.0: Add Release Notes and Versioned Docs (#1349) release 0.4.0: add release notes and versioned docs (cherry picked from commit 843d5557fa3d3da479f69ecf685cd001c97a9bf1) Signed-off-by: AliceProxy --- VERSION | 2 +- docs/latest/releases/README.md | 2 + docs/latest/releases/v0.4.md | 61 ++ docs/v0.4/about_docs.rst | 9 + docs/v0.4/api/config_types.md | 448 +++++++++++ docs/v0.4/api/extension_types.md | 229 ++++++ docs/v0.4/api_docs.rst | 10 + docs/v0.4/conf.py | 43 ++ docs/v0.4/design/bootstrap.md | 379 +++++++++ docs/v0.4/design/config-api.md | 350 +++++++++ docs/v0.4/design/egctl.md | 57 ++ docs/v0.4/design/extending-envoy-gateway.md | 324 ++++++++ docs/v0.4/design/gatewayapi-support.md | 119 +++ docs/v0.4/design/gatewayapi-translator.md | 250 ++++++ docs/v0.4/design/rate-limit.md | 346 +++++++++ docs/v0.4/design/request-authentication.md | 513 +++++++++++++ docs/v0.4/design/roadmap.md | 80 ++ docs/v0.4/design/system-design.md | 171 +++++ docs/v0.4/design/tcp-udp-design.md | 47 ++ docs/v0.4/design/watching.md | 117 +++ docs/v0.4/design_docs.rst | 19 + docs/v0.4/dev/CODEOWNERS.md | 15 + docs/v0.4/dev/CODE_OF_CONDUCT.md | 3 + docs/v0.4/dev/CONTRIBUTING.md | 186 +++++ docs/v0.4/dev/DOCS.md | 63 ++ docs/v0.4/dev/GOALS.md | 1 + docs/v0.4/dev/README.md | 156 ++++ docs/v0.4/dev/releasing.md | 195 +++++ docs/v0.4/dev_docs.rst | 15 + docs/v0.4/get_involved.rst | 9 + docs/v0.4/images/architecture.png | Bin 0 -> 449265 bytes docs/v0.4/images/extension-example.png | Bin 0 -> 43264 bytes docs/v0.4/index.rst | 34 + docs/v0.4/intro/compatibility.rst | 23 + docs/v0.4/presentations.md | 10 + docs/v0.4/releases.rst | 11 + docs/v0.4/releases/README.md | 43 ++ docs/v0.4/releases/v0.2.md | 50 ++ docs/v0.4/releases/v0.3.md | 50 ++ docs/v0.4/releases/v0.4.md | 61 ++ docs/v0.4/roadmap.rst | 9 + docs/v0.4/user/authn.md | 94 +++ docs/v0.4/user/customize-envoyproxy.md | 252 ++++++ docs/v0.4/user/deployment-mode.md | 395 ++++++++++ docs/v0.4/user/egctl.md | 804 ++++++++++++++++++++ docs/v0.4/user/grpc-routing.md | 147 ++++ docs/v0.4/user/http-redirect.md | 127 ++++ docs/v0.4/user/http-request-headers.md | 283 +++++++ docs/v0.4/user/http-response-headers.md | 281 +++++++ docs/v0.4/user/http-routing.md | 135 ++++ docs/v0.4/user/http-traffic-splitting.md | 311 ++++++++ docs/v0.4/user/http-urlrewrite.md | 295 +++++++ docs/v0.4/user/quickstart.md | 97 +++ docs/v0.4/user/rate-limit.md | 631 +++++++++++++++ docs/v0.4/user/secure-gateways.md | 368 +++++++++ docs/v0.4/user/tcp-routing.md | 302 ++++++++ docs/v0.4/user/tls-passthrough.md | 117 +++ docs/v0.4/user/udp-routing.md | 154 ++++ docs/v0.4/user_docs.rst | 25 + release-notes/v0.4.0.yaml | 64 ++ 60 files changed, 9391 insertions(+), 1 deletion(-) create mode 100644 docs/latest/releases/v0.4.md create mode 100644 docs/v0.4/about_docs.rst create mode 100644 docs/v0.4/api/config_types.md create mode 100644 docs/v0.4/api/extension_types.md create mode 100644 docs/v0.4/api_docs.rst create mode 100644 docs/v0.4/conf.py create mode 100644 docs/v0.4/design/bootstrap.md create mode 100644 docs/v0.4/design/config-api.md create mode 100644 docs/v0.4/design/egctl.md create mode 100644 docs/v0.4/design/extending-envoy-gateway.md create mode 100644 docs/v0.4/design/gatewayapi-support.md create mode 100644 docs/v0.4/design/gatewayapi-translator.md create mode 100644 docs/v0.4/design/rate-limit.md create mode 100644 docs/v0.4/design/request-authentication.md create mode 100644 docs/v0.4/design/roadmap.md create mode 100644 docs/v0.4/design/system-design.md create mode 100644 docs/v0.4/design/tcp-udp-design.md create mode 100644 docs/v0.4/design/watching.md create mode 100644 docs/v0.4/design_docs.rst create mode 100644 docs/v0.4/dev/CODEOWNERS.md create mode 100644 docs/v0.4/dev/CODE_OF_CONDUCT.md create mode 100644 docs/v0.4/dev/CONTRIBUTING.md create mode 100644 docs/v0.4/dev/DOCS.md create mode 120000 docs/v0.4/dev/GOALS.md create mode 100644 docs/v0.4/dev/README.md create mode 100644 docs/v0.4/dev/releasing.md create mode 100644 docs/v0.4/dev_docs.rst create mode 100644 docs/v0.4/get_involved.rst create mode 100644 docs/v0.4/images/architecture.png create mode 100644 docs/v0.4/images/extension-example.png create mode 100644 docs/v0.4/index.rst create mode 100644 docs/v0.4/intro/compatibility.rst create mode 100644 docs/v0.4/presentations.md create mode 100644 docs/v0.4/releases.rst create mode 100644 docs/v0.4/releases/README.md create mode 100644 docs/v0.4/releases/v0.2.md create mode 100644 docs/v0.4/releases/v0.3.md create mode 100644 docs/v0.4/releases/v0.4.md create mode 100644 docs/v0.4/roadmap.rst create mode 100644 docs/v0.4/user/authn.md create mode 100644 docs/v0.4/user/customize-envoyproxy.md create mode 100644 docs/v0.4/user/deployment-mode.md create mode 100644 docs/v0.4/user/egctl.md create mode 100644 docs/v0.4/user/grpc-routing.md create mode 100644 docs/v0.4/user/http-redirect.md create mode 100644 docs/v0.4/user/http-request-headers.md create mode 100644 docs/v0.4/user/http-response-headers.md create mode 100644 docs/v0.4/user/http-routing.md create mode 100644 docs/v0.4/user/http-traffic-splitting.md create mode 100644 docs/v0.4/user/http-urlrewrite.md create mode 100644 docs/v0.4/user/quickstart.md create mode 100644 docs/v0.4/user/rate-limit.md create mode 100644 docs/v0.4/user/secure-gateways.md create mode 100644 docs/v0.4/user/tcp-routing.md create mode 100644 docs/v0.4/user/tls-passthrough.md create mode 100644 docs/v0.4/user/udp-routing.md create mode 100644 docs/v0.4/user_docs.rst create mode 100644 release-notes/v0.4.0.yaml diff --git a/VERSION b/VERSION index 7d5665ab175..1811f96e317 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.4.0-rc.1 +v0.4 diff --git a/docs/latest/releases/README.md b/docs/latest/releases/README.md index 3f6df9e7ea6..2ca374ca69d 100644 --- a/docs/latest/releases/README.md +++ b/docs/latest/releases/README.md @@ -25,6 +25,7 @@ communications with the Envoy Gateway community, and the mechanics of the releas |:-------:|:--------------------------------------------------------------:| | 2022 Q4 | Daneyon Hansen ([danehans](https://github.com/danehans)) | | 2023 Q1 | Xunzhuo Liu ([Xunzhuo](https://github.com/Xunzhuo)) | +| 2023 Q2 | Alice Wasko ([AliceProxy](https://github.com/AliceProxy)) | ## Release Schedule @@ -35,6 +36,7 @@ In order to align with the Envoy Proxy [release schedule][], Envoy Gateway relea |:-------:|:-----------:|:-----------:|:----------:|:-----------:| | 0.2.0 | 2022/10/22 | 2022/10/20 | -2 day | 2023/4/20 | | 0.3.0 | 2023/01/22 | 2023/02/09 | +17 day | 2023/08/09 | +| 0.3.0 | 2023/04/22 | 2023/04/24 | +2 day | 2023/10/24 | [v2.0.0 spec]: https://semver.org/spec/v2.0.0.html [release guide]: ../dev/releasing.md diff --git a/docs/latest/releases/v0.4.md b/docs/latest/releases/v0.4.md new file mode 100644 index 00000000000..81a9bc3a0ed --- /dev/null +++ b/docs/latest/releases/v0.4.md @@ -0,0 +1,61 @@ +--- +title: Announcing Envoy Gateway v0.4 +linktitle: v0.4 +subtitle: Major Update +description: Envoy Gateway v0.4 release announcement. +publishdate: 2023-04-24 +release: v0.4.0 +skip_list: true +aliases: +- /releases/v0.4 +- /releases/v0.4.0 +--- +# Envoy Gateway Release v0.4 + +We are pleased to announce the release of Envoy Gateway v0.4! + +This is the third functional release of Envoy Gateway. We would like to thank the entire Envoy Gateway community for +helping publish the release. + +| [Release Notes][] | [Docs][docs] | [Compatibility Matrix][matrix] | [Download][] | +|-------------------|--------------|--------------------------------|--------------| + +## What's New + +The release adds a ton of features and functionality. Here are some highlights: + +### Upgrade Gateway API Dependency + ++ Upgraded to Gateway API v0.6.2 + +### Add Helm Support + ++ Installation of Envoy Gateway can now be done through helm + +### Add egctl CLI Tool + ++ Added egctl Support for Dry Runs of Gateway API Config ++ Added egctl Support for Dumping Envoy Proxy xDS Resources + +### Add Support for extending Envoy Gateway + ++ Added Initial Framework for Building an Extension on top of Envoy Gateway + +### Ratelimiting + ++ Added Support for Ratelimiting Based On IP Subnet + +### API Updates + ++ Added Support for Custom Envoy Proxy Bootstrap Config ++ Added Support for Configuring the Envoy Proxy Image and Service ++ Added Support for Configuring Annotations, Resources, and Securitycontext Settings on Ratelimit Infra and Envoy Proxy ++ Added Support for Using Multiple Certificates on a Single Fully Qualified Domain Name ++ Envoy Proxy Pod and Container SecurityContext is now Configurable ++ Added Support for Service Method Match in GRPCRoute ++ Added EDS Support + +[Release Notes]: https://github.com/envoyproxy/gateway/blob/main/release-notes/v0.4.0.yaml +[matrix]: https://gateway.envoyproxy.io/v0.4.0/intro/compatibility.html +[docs]: https://gateway.envoyproxy.io/v0.4.0/index.html +[Download]: https://github.com/envoyproxy/gateway/releases/tag/v0.4.0 diff --git a/docs/v0.4/about_docs.rst b/docs/v0.4/about_docs.rst new file mode 100644 index 00000000000..ecaa28247b9 --- /dev/null +++ b/docs/v0.4/about_docs.rst @@ -0,0 +1,9 @@ +About the Documentation +======================= + +Learn how to contribute to Envoy Gateway documentation. + +.. toctree:: + :maxdepth: 1 + + dev/DOCS diff --git a/docs/v0.4/api/config_types.md b/docs/v0.4/api/config_types.md new file mode 100644 index 00000000000..87118b7dbe4 --- /dev/null +++ b/docs/v0.4/api/config_types.md @@ -0,0 +1,448 @@ +# API Reference + +## Packages +- [config.gateway.envoyproxy.io/v1alpha1](#configgatewayenvoyproxyiov1alpha1) + + +## config.gateway.envoyproxy.io/v1alpha1 + +Package v1alpha1 contains API schema definitions for the config.gateway.envoyproxy.io +API group. + + +### Resource Types +- [EnvoyGateway](#envoygateway) +- [EnvoyProxy](#envoyproxy) + + + +## EnvoyGateway + + + +EnvoyGateway is the schema for the envoygateways API. + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `config.gateway.envoyproxy.io/v1alpha1` +| `kind` _string_ | `EnvoyGateway` +| `EnvoyGatewaySpec` _[EnvoyGatewaySpec](#envoygatewayspec)_ | EnvoyGatewaySpec defines the desired state of EnvoyGateway. | + + +## EnvoyGatewayFileProvider + + + +EnvoyGatewayFileProvider defines configuration for the File provider. + +_Appears in:_ +- [EnvoyGatewayProvider](#envoygatewayprovider) + + + +## EnvoyGatewayKubernetesProvider + + + +EnvoyGatewayKubernetesProvider defines configuration for the Kubernetes provider. + +_Appears in:_ +- [EnvoyGatewayProvider](#envoygatewayprovider) + +| Field | Description | +| --- | --- | +| `rateLimitDeployment` _[KubernetesDeploymentSpec](#kubernetesdeploymentspec)_ | RateLimitDeployment defines the desired state of the Envoy ratelimit deployment resource. If unspecified, default settings for the manged Envoy ratelimit deployment resource are applied. | + + +## EnvoyGatewayProvider + + + +EnvoyGatewayProvider defines the desired configuration of a provider. + +_Appears in:_ +- [EnvoyGatewaySpec](#envoygatewayspec) + +| Field | Description | +| --- | --- | +| `type` _[ProviderType](#providertype)_ | Type is the type of provider to use. Supported types are "Kubernetes". | +| `kubernetes` _[EnvoyGatewayKubernetesProvider](#envoygatewaykubernetesprovider)_ | Kubernetes defines the configuration of the Kubernetes provider. Kubernetes provides runtime configuration via the Kubernetes API. | +| `file` _[EnvoyGatewayFileProvider](#envoygatewayfileprovider)_ | File defines the configuration of the File provider. File provides runtime configuration defined by one or more files. This type is not implemented until https://github.com/envoyproxy/gateway/issues/1001 is fixed. | + + +## EnvoyGatewaySpec + + + +EnvoyGatewaySpec defines the desired state of Envoy Gateway. + +_Appears in:_ +- [EnvoyGateway](#envoygateway) + +| Field | Description | +| --- | --- | +| `gateway` _[Gateway](#gateway)_ | Gateway defines desired Gateway API specific configuration. If unset, default configuration parameters will apply. | +| `provider` _[EnvoyGatewayProvider](#envoygatewayprovider)_ | Provider defines the desired provider and provider-specific configuration. If unspecified, the Kubernetes provider is used with default configuration parameters. | +| `rateLimit` _[RateLimit](#ratelimit)_ | RateLimit defines the configuration associated with the Rate Limit service deployed by Envoy Gateway required to implement the Global Rate limiting functionality. The specific rate limit service used here is the reference implementation in Envoy. For more details visit https://github.com/envoyproxy/ratelimit. This configuration is unneeded for "Local" rate limiting. | +| `extension` _[Extension](#extension)_ | Extension defines an extension to register for the Envoy Gateway Control Plane. | + + +## EnvoyProxy + + + +EnvoyProxy is the schema for the envoyproxies API. + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `config.gateway.envoyproxy.io/v1alpha1` +| `kind` _string_ | `EnvoyProxy` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[EnvoyProxySpec](#envoyproxyspec)_ | EnvoyProxySpec defines the desired state of EnvoyProxy. | + + +## EnvoyProxyKubernetesProvider + + + +EnvoyProxyKubernetesProvider defines configuration for the Kubernetes resource provider. + +_Appears in:_ +- [EnvoyProxyProvider](#envoyproxyprovider) + +| Field | Description | +| --- | --- | +| `envoyDeployment` _[KubernetesDeploymentSpec](#kubernetesdeploymentspec)_ | EnvoyDeployment defines the desired state of the Envoy deployment resource. If unspecified, default settings for the manged Envoy deployment resource are applied. | +| `envoyService` _[KubernetesServiceSpec](#kubernetesservicespec)_ | EnvoyService defines the desired state of the Envoy service resource. If unspecified, default settings for the manged Envoy service resource are applied. | + + +## EnvoyProxyProvider + + + +EnvoyProxyProvider defines the desired state of a resource provider. + +_Appears in:_ +- [EnvoyProxySpec](#envoyproxyspec) + +| Field | Description | +| --- | --- | +| `type` _[ProviderType](#providertype)_ | Type is the type of resource provider to use. A resource provider provides infrastructure resources for running the data plane, e.g. Envoy proxy, and optional auxiliary control planes. Supported types are "Kubernetes". | +| `kubernetes` _[EnvoyProxyKubernetesProvider](#envoyproxykubernetesprovider)_ | Kubernetes defines the desired state of the Kubernetes resource provider. Kubernetes provides infrastructure resources for running the data plane, e.g. Envoy proxy. If unspecified and type is "Kubernetes", default settings for managed Kubernetes resources are applied. | + + +## EnvoyProxySpec + + + +EnvoyProxySpec defines the desired state of EnvoyProxy. + +_Appears in:_ +- [EnvoyProxy](#envoyproxy) + +| Field | Description | +| --- | --- | +| `provider` _[EnvoyProxyProvider](#envoyproxyprovider)_ | Provider defines the desired resource provider and provider-specific configuration. If unspecified, the "Kubernetes" resource provider is used with default configuration parameters. | +| `logging` _[ProxyLogging](#proxylogging)_ | Logging defines logging parameters for managed proxies. If unspecified, default settings apply. This type is not implemented until https://github.com/envoyproxy/gateway/issues/280 is fixed. | +| `bootstrap` _string_ | Bootstrap defines the Envoy Bootstrap as a YAML string. Visit https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/bootstrap/v3/bootstrap.proto#envoy-v3-api-msg-config-bootstrap-v3-bootstrap to learn more about the syntax. If set, this is the Bootstrap configuration used for the managed Envoy Proxy fleet instead of the default Bootstrap configuration set by Envoy Gateway. Some fields within the Bootstrap that are required to communicate with the xDS Server (Envoy Gateway) and receive xDS resources from it are not configurable and will result in the `EnvoyProxy` resource being rejected. Backward compatibility across minor versions is not guaranteed. We strongly recommend using `egctl x translate` to generate a `EnvoyProxy` resource with the `Bootstrap` field set to the default Bootstrap configuration used. You can edit this configuration, and rerun `egctl x translate` to ensure there are no validation errors. | + + + + +## Extension + + + +Extension defines the configuration for registering an extension to the Envoy Gateway control plane. + +_Appears in:_ +- [EnvoyGatewaySpec](#envoygatewayspec) + +| Field | Description | +| --- | --- | +| `resources` _[GroupVersionKind](#groupversionkind) array_ | Resources defines the set of K8s resources the extension will handle. | +| `hooks` _[ExtensionHooks](#extensionhooks)_ | Hooks defines the set of hooks the extension supports | +| `service` _[ExtensionService](#extensionservice)_ | Service defines the configuration of the extension service that the Envoy Gateway Control Plane will call through extension hooks. | + + +## ExtensionHooks + + + +ExtensionHooks defines extension hooks across all supported runners + +_Appears in:_ +- [Extension](#extension) + +| Field | Description | +| --- | --- | +| `xdsTranslator` _[XDSTranslatorHooks](#xdstranslatorhooks)_ | XDSTranslator defines all the supported extension hooks for the xds-translator runner | + + +## ExtensionService + + + +ExtensionService defines the configuration for connecting to a registered extension service. + +_Appears in:_ +- [Extension](#extension) + +| Field | Description | +| --- | --- | +| `host` _string_ | Host define the extension service hostname. | +| `port` _integer_ | Port defines the port the extension service is exposed on. | +| `tls` _[ExtensionTLS](#extensiontls)_ | TLS defines TLS configuration for communication between Envoy Gateway and the extension service. | + + +## ExtensionTLS + + + +ExtensionTLS defines the TLS configuration when connecting to an extension service + +_Appears in:_ +- [ExtensionService](#extensionservice) + +| Field | Description | +| --- | --- | +| `certificateRef` _[SecretObjectReference](#secretobjectreference)_ | CertificateRef contains a references to objects (Kubernetes objects or otherwise) that contains a TLS certificate and private keys. These certificates are used to establish a TLS handshake to the extension server. + CertificateRef can only reference a Kubernetes Secret at this time. | + + +## Gateway + + + +Gateway defines the desired Gateway API configuration of Envoy Gateway. + +_Appears in:_ +- [EnvoyGatewaySpec](#envoygatewayspec) + +| Field | Description | +| --- | --- | +| `controllerName` _string_ | ControllerName defines the name of the Gateway API controller. If unspecified, defaults to "gateway.envoyproxy.io/gatewayclass-controller". See the following for additional details: https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.GatewayClass | + + +## GroupVersionKind + + + +GroupVersionKind unambiguously identifies a Kind. It can be converted to k8s.io/apimachinery/pkg/runtime/schema.GroupVersionKind + +_Appears in:_ +- [Extension](#extension) + +| Field | Description | +| --- | --- | +| `group` _string_ | | +| `version` _string_ | | +| `kind` _string_ | | + + +## KubernetesContainerSpec + + + +KubernetesContainerSpec defines the desired state of the Kubernetes container resource. + +_Appears in:_ +- [KubernetesDeploymentSpec](#kubernetesdeploymentspec) + +| Field | Description | +| --- | --- | +| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#resourcerequirements-v1-core)_ | Resources required by this container. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ | +| `securityContext` _[SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#securitycontext-v1-core)_ | SecurityContext defines the security options the container should be run with. If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ | +| `image` _string_ | Image specifies the EnvoyProxy container image to be used, instead of the default image. | + + +## KubernetesDeploymentSpec + + + +KubernetesDeploymentSpec defines the desired state of the Kubernetes deployment resource. + +_Appears in:_ +- [EnvoyGatewayKubernetesProvider](#envoygatewaykubernetesprovider) +- [EnvoyProxyKubernetesProvider](#envoyproxykubernetesprovider) + +| Field | Description | +| --- | --- | +| `replicas` _integer_ | Replicas is the number of desired pods. Defaults to 1. | +| `pod` _[KubernetesPodSpec](#kubernetespodspec)_ | Pod defines the desired annotations and securityContext of container. | +| `container` _[KubernetesContainerSpec](#kubernetescontainerspec)_ | Container defines the resources and securityContext of container. | + + +## KubernetesPodSpec + + + +KubernetesPodSpec defines the desired state of the Kubernetes pod resource. + +_Appears in:_ +- [KubernetesDeploymentSpec](#kubernetesdeploymentspec) + +| Field | Description | +| --- | --- | +| `annotations` _object (keys:string, values:string)_ | Annotations are the annotations that should be appended to the pods. By default, no pod annotations are appended. | +| `securityContext` _[PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#podsecuritycontext-v1-core)_ | SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field. | + + +## KubernetesServiceSpec + + + +KubernetesServiceSpec defines the desired state of the Kubernetes service resource. + +_Appears in:_ +- [EnvoyProxyKubernetesProvider](#envoyproxykubernetesprovider) + +| Field | Description | +| --- | --- | +| `annotations` _object (keys:string, values:string)_ | Annotations that should be appended to the service. By default, no annotations are appended. | +| `type` _[ServiceType](#servicetype)_ | Type determines how the Service is exposed. Defaults to LoadBalancer. Valid options are ClusterIP and LoadBalancer. "LoadBalancer" means a service will be exposed via an external load balancer (if the cloud provider supports it). "ClusterIP" means a service will only be accessible inside the cluster, via the cluster IP. | + + +## LogComponent + +_Underlying type:_ `string` + +LogComponent defines a component that supports a configured logging level. This type is not implemented until https://github.com/envoyproxy/gateway/issues/280 is fixed. + +_Appears in:_ +- [ProxyLogging](#proxylogging) + + + +## LogLevel + +_Underlying type:_ `string` + +LogLevel defines a log level for system logs. This type is not implemented until https://github.com/envoyproxy/gateway/issues/280 is fixed. + +_Appears in:_ +- [ProxyLogging](#proxylogging) + + + +## ProviderType + +_Underlying type:_ `string` + +ProviderType defines the types of providers supported by Envoy Gateway. + +_Appears in:_ +- [EnvoyGatewayProvider](#envoygatewayprovider) +- [EnvoyProxyProvider](#envoyproxyprovider) + + + +## ProxyLogging + + + +ProxyLogging defines logging parameters for managed proxies. This type is not implemented until https://github.com/envoyproxy/gateway/issues/280 is fixed. + +_Appears in:_ +- [EnvoyProxySpec](#envoyproxyspec) + +| Field | Description | +| --- | --- | +| `level` _object (keys:[LogComponent](#logcomponent), values:[LogLevel](#loglevel))_ | Level is a map of logging level per component, where the component is the key and the log level is the value. If unspecified, defaults to "System: Info". | + + +## RateLimit + + + +RateLimit defines the configuration associated with the Rate Limit Service used for Global Rate Limiting. + +_Appears in:_ +- [EnvoyGatewaySpec](#envoygatewayspec) + +| Field | Description | +| --- | --- | +| `backend` _[RateLimitDatabaseBackend](#ratelimitdatabasebackend)_ | Backend holds the configuration associated with the database backend used by the rate limit service to store state associated with global ratelimiting. | + + +## RateLimitDatabaseBackend + + + +RateLimitDatabaseBackend defines the configuration associated with the database backend used by the rate limit service. + +_Appears in:_ +- [RateLimit](#ratelimit) + +| Field | Description | +| --- | --- | +| `type` _[RateLimitDatabaseBackendType](#ratelimitdatabasebackendtype)_ | Type is the type of database backend to use. Supported types are: * Redis: Connects to a Redis database. | +| `redis` _[RateLimitRedisSettings](#ratelimitredissettings)_ | Redis defines the settings needed to connect to a Redis database. | + + +## RateLimitDatabaseBackendType + +_Underlying type:_ `string` + +RateLimitDatabaseBackendType specifies the types of database backend to be used by the rate limit service. + +_Appears in:_ +- [RateLimitDatabaseBackend](#ratelimitdatabasebackend) + + + +## RateLimitRedisSettings + + + +RateLimitRedisSettings defines the configuration for connecting to a Redis database. + +_Appears in:_ +- [RateLimitDatabaseBackend](#ratelimitdatabasebackend) + +| Field | Description | +| --- | --- | +| `url` _string_ | URL of the Redis Database. | + + +## ServiceType + +_Underlying type:_ `string` + +ServiceType string describes ingress methods for a service + +_Appears in:_ +- [KubernetesServiceSpec](#kubernetesservicespec) + + + +## XDSTranslatorHook + +_Underlying type:_ `string` + +XDSTranslatorHook defines the types of hooks that an Envoy Gateway extension may support for the xds-translator + +_Appears in:_ +- [XDSTranslatorHooks](#xdstranslatorhooks) + + + +## XDSTranslatorHooks + + + +XDSTranslatorHooks contains all the pre and post hooks for the xds-translator runner. + +_Appears in:_ +- [ExtensionHooks](#extensionhooks) + +| Field | Description | +| --- | --- | +| `pre` _[XDSTranslatorHook](#xdstranslatorhook) array_ | | +| `post` _[XDSTranslatorHook](#xdstranslatorhook) array_ | | + + diff --git a/docs/v0.4/api/extension_types.md b/docs/v0.4/api/extension_types.md new file mode 100644 index 00000000000..1b1da8e4cdc --- /dev/null +++ b/docs/v0.4/api/extension_types.md @@ -0,0 +1,229 @@ +# API Reference + +## Packages +- [gateway.envoyproxy.io/v1alpha1](#gatewayenvoyproxyiov1alpha1) + + +## gateway.envoyproxy.io/v1alpha1 + +Package v1alpha1 contains API schema definitions for the gateway.envoyproxy.io API group. + + +### Resource Types +- [AuthenticationFilter](#authenticationfilter) +- [RateLimitFilter](#ratelimitfilter) + + + +## AuthenticationFilter + + + + + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `gateway.envoyproxy.io/v1alpha1` +| `kind` _string_ | `AuthenticationFilter` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[AuthenticationFilterSpec](#authenticationfilterspec)_ | Spec defines the desired state of the AuthenticationFilter type. | + + +## AuthenticationFilterSpec + + + +AuthenticationFilterSpec defines the desired state of the AuthenticationFilter type. + +_Appears in:_ +- [AuthenticationFilter](#authenticationfilter) + +| Field | Description | +| --- | --- | +| `type` _[AuthenticationFilterType](#authenticationfiltertype)_ | Type defines the type of authentication provider to use. Supported provider types are "JWT". | +| `jwtProviders` _[JwtAuthenticationFilterProvider](#jwtauthenticationfilterprovider) array_ | JWT defines the JSON Web Token (JWT) authentication provider type. When multiple jwtProviders are specified, the JWT is considered valid if any of the providers successfully validate the JWT. For additional details, see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/jwt_authn_filter.html. | + + +## AuthenticationFilterType + +_Underlying type:_ `string` + +AuthenticationFilterType is a type of authentication provider. + +_Appears in:_ +- [AuthenticationFilterSpec](#authenticationfilterspec) + + + +## GlobalRateLimit + + + +GlobalRateLimit defines global rate limit configuration. + +_Appears in:_ +- [RateLimitFilterSpec](#ratelimitfilterspec) + +| Field | Description | +| --- | --- | +| `rules` _[RateLimitRule](#ratelimitrule) array_ | Rules are a list of RateLimit selectors and limits. Each rule and its associated limit is applied in a mutually exclusive way i.e. if multiple rules get selected, each of their associated limits get applied, so a single traffic request might increase the rate limit counters for multiple rules if selected. | + + +## HeaderMatch + + + +HeaderMatch defines the match attributes within the HTTP Headers of the request. + +_Appears in:_ +- [RateLimitSelectCondition](#ratelimitselectcondition) + +| Field | Description | +| --- | --- | +| `type` _[HeaderMatchType](#headermatchtype)_ | Type specifies how to match against the value of the header. | +| `name` _string_ | Name of the HTTP header. | +| `value` _string_ | Value within the HTTP header. Due to the case-insensitivity of header names, "foo" and "Foo" are considered equivalent. Do not set this field when Type="Distinct", implying matching on any/all unique values within the header. | + + +## HeaderMatchType + +_Underlying type:_ `string` + +HeaderMatchType specifies the semantics of how HTTP header values should be compared. Valid HeaderMatchType values are "Exact", "RegularExpression", and "Distinct". + +_Appears in:_ +- [HeaderMatch](#headermatch) + + + +## JwtAuthenticationFilterProvider + + + +JwtAuthenticationFilterProvider defines the JSON Web Token (JWT) authentication provider type and how JWTs should be verified: + +_Appears in:_ +- [AuthenticationFilterSpec](#authenticationfilterspec) + +| Field | Description | +| --- | --- | +| `name` _string_ | Name defines a unique name for the JWT provider. A name can have a variety of forms, including RFC1123 subdomains, RFC 1123 labels, or RFC 1035 labels. | +| `issuer` _string_ | Issuer is the principal that issued the JWT and takes the form of a URL or email address. For additional details, see https://tools.ietf.org/html/rfc7519#section-4.1.1 for URL format and https://rfc-editor.org/rfc/rfc5322.html for email format. If not provided, the JWT issuer is not checked. | +| `audiences` _string array_ | Audiences is a list of JWT audiences allowed access. For additional details, see https://tools.ietf.org/html/rfc7519#section-4.1.3. If not provided, JWT audiences are not checked. | +| `remoteJWKS` _[RemoteJWKS](#remotejwks)_ | RemoteJWKS defines how to fetch and cache JSON Web Key Sets (JWKS) from a remote HTTP/HTTPS endpoint. | + + +## RateLimitFilter + + + +RateLimitFilter allows the user to limit the number of incoming requests to a predefined value based on attributes within the traffic flow. + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `gateway.envoyproxy.io/v1alpha1` +| `kind` _string_ | `RateLimitFilter` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[RateLimitFilterSpec](#ratelimitfilterspec)_ | Spec defines the desired state of RateLimitFilter. | + + +## RateLimitFilterSpec + + + +RateLimitFilterSpec defines the desired state of RateLimitFilter. + +_Appears in:_ +- [RateLimitFilter](#ratelimitfilter) + +| Field | Description | +| --- | --- | +| `type` _[RateLimitType](#ratelimittype)_ | Type decides the scope for the RateLimits. Valid RateLimitType values are "Global". | +| `global` _[GlobalRateLimit](#globalratelimit)_ | Global defines global rate limit configuration. | + + +## RateLimitRule + + + +RateLimitRule defines the semantics for matching attributes from the incoming requests, and setting limits for them. + +_Appears in:_ +- [GlobalRateLimit](#globalratelimit) + +| Field | Description | +| --- | --- | +| `clientSelectors` _[RateLimitSelectCondition](#ratelimitselectcondition) array_ | ClientSelectors holds the list of select conditions to select specific clients using attributes from the traffic flow. All individual select conditions must hold True for this rule and its limit to be applied. If this field is empty, it is equivalent to True, and the limit is applied. | +| `limit` _[RateLimitValue](#ratelimitvalue)_ | Limit holds the rate limit values. This limit is applied for traffic flows when the selectors compute to True, causing the request to be counted towards the limit. The limit is enforced and the request is ratelimited, i.e. a response with 429 HTTP status code is sent back to the client when the selected requests have reached the limit. | + + +## RateLimitSelectCondition + + + +RateLimitSelectCondition specifies the attributes within the traffic flow that can be used to select a subset of clients to be ratelimited. All the individual conditions must hold True for the overall condition to hold True. + +_Appears in:_ +- [RateLimitRule](#ratelimitrule) + +| Field | Description | +| --- | --- | +| `headers` _[HeaderMatch](#headermatch) array_ | Headers is a list of request headers to match. Multiple header values are ANDed together, meaning, a request MUST match all the specified headers. | +| `sourceIP` _string_ | SourceIP is the IP CIDR that represents the range of Source IP Addresses of the client. These could also be the intermediate addresses through which the request has flown through and is part of the `X-Forwarded-For` header. For example, `192.168.0.1/32`, `192.168.0.0/24`, `001:db8::/64`. All IP Addresses within the specified SourceIP CIDR are treated as a single client selector and share the same rate limit bucket. | + + +## RateLimitType + +_Underlying type:_ `string` + +RateLimitType specifies the types of RateLimiting. + +_Appears in:_ +- [RateLimitFilterSpec](#ratelimitfilterspec) + + + +## RateLimitUnit + +_Underlying type:_ `string` + +RateLimitUnit specifies the intervals for setting rate limits. Valid RateLimitUnit values are "Second", "Minute", "Hour", and "Day". + +_Appears in:_ +- [RateLimitValue](#ratelimitvalue) + + + +## RateLimitValue + + + +RateLimitValue defines the limits for rate limiting. + +_Appears in:_ +- [RateLimitRule](#ratelimitrule) + +| Field | Description | +| --- | --- | +| `requests` _integer_ | | +| `unit` _[RateLimitUnit](#ratelimitunit)_ | | + + +## RemoteJWKS + + + +RemoteJWKS defines how to fetch and cache JSON Web Key Sets (JWKS) from a remote HTTP/HTTPS endpoint. + +_Appears in:_ +- [JwtAuthenticationFilterProvider](#jwtauthenticationfilterprovider) + +| Field | Description | +| --- | --- | +| `uri` _string_ | URI is the HTTPS URI to fetch the JWKS. Envoy's system trust bundle is used to validate the server certificate. | + + diff --git a/docs/v0.4/api_docs.rst b/docs/v0.4/api_docs.rst new file mode 100644 index 00000000000..b470527c50c --- /dev/null +++ b/docs/v0.4/api_docs.rst @@ -0,0 +1,10 @@ +API Docs +============== + +API reference documentation for Envoy Gateway. + +.. toctree:: + :maxdepth: 1 + + Config APIs + Extension APIs diff --git a/docs/v0.4/conf.py b/docs/v0.4/conf.py new file mode 100644 index 00000000000..76ef5717548 --- /dev/null +++ b/docs/v0.4/conf.py @@ -0,0 +1,43 @@ +import os + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.duration', + 'sphinx.ext.autosectionlabel', + 'myst_parser', +] + +autosectionlabel_prefix_document = True +myst_heading_anchors = 3 + +html_theme = 'alabaster' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +version = os.environ["BUILD_VERSION"] +envoyVersion = os.environ["ENVOY_PROXY_VERSION"] +gatewayAPIVersion = os.environ["GATEWAYAPI_VERSION"] + +project = 'Envoy Gateway' +author = 'Envoy Gateway Project Authors' + +copyright = 'Envoy Gateway Project Authors | GitHub | Latest Docs' + +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} + +variables_to_export = [ + "version", + "envoyVersion", + "gatewayAPIVersion", +] + +frozen_locals = dict(locals()) +rst_epilog = '\n'.join(map(lambda x: f".. |{x}| replace:: {frozen_locals[x]}", variables_to_export)) +del frozen_locals diff --git a/docs/v0.4/design/bootstrap.md b/docs/v0.4/design/bootstrap.md new file mode 100644 index 00000000000..30f1b52116f --- /dev/null +++ b/docs/v0.4/design/bootstrap.md @@ -0,0 +1,379 @@ +# Bootstrap Design + +## Overview + +[Issue 31][] specifies the need for allowing advanced users to specify their custom +Envoy Bootstrap configuration rather than using the default Bootstrap configuration +defined in Envoy Gateway. This allows advanced users to extend Envoy Gateway and +support their custom use cases such setting up tracing and stats configuration +that is not supported by Envoy Gateway. + +## Goals + +* Define an API field to allow a user to specify a custom Bootstrap +* Provide tooling to allow the user to generate the default Bootstrap configuration + as well as validate their custom Bootstrap. + +## Non Goals + +* Allow user to configure only a section of the Bootstrap + +## API + +Leverage the existing [EnvoyProxy][] resource which can be attached to the [GatewayClass][] using +the [parametersRef][] field, and define a `Bootstrap` field within the resource. If this field is set, +the value is used as the Bootstrap configuration for all managed Envoy Proxies created by Envoy Gateway. + +```go +// EnvoyProxySpec defines the desired state of EnvoyProxy. +type EnvoyProxySpec struct { + ...... + // Bootstrap defines the Envoy Bootstrap as a YAML string. + // Visit https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/bootstrap/v3/bootstrap.proto#envoy-v3-api-msg-config-bootstrap-v3-bootstrap + // to learn more about the syntax. + // If set, this is the Bootstrap configuration used for the managed Envoy Proxy fleet instead of the default Bootstrap configuration + // set by Envoy Gateway. + // Some fields within the Bootstrap that are required to communicate with the xDS Server (Envoy Gateway) and receive xDS resources + // from it are not configurable and will result in the `EnvoyProxy` resource being rejected. + // Backward compatibility across minor versions is not guaranteed. + // We strongly recommend using `egctl x translate` to generate a `EnvoyProxy` resource with the `Bootstrap` field set to the default + // Bootstrap configuration used. You can edit this configuration, and rerun `egctl x translate` to ensure there are no validation errors. + // + // +optional + Bootstrap *string `json:"bootstrap,omitempty"` +} +``` + +## Tooling + +A CLI tool `egctl x translate` will be provided to the user to help generate a working Bootstrap configuration. +Here is an example where a user inputs a `GatewayClass` and the CLI generates the `EnvoyProxy` resource with the `Bootstrap` field populated. + +``` +cat < /etc/envoy-gateway/config.yaml +apiVersion: config.gateway.envoyproxy.io/v1alpha1 +kind: EnvoyGateway +provider: + type: Kubernetes + kubernetes: {} +EOF +``` + +This configuration will cause Envoy Gateway to use the Kubernetes provider with default configuration parameters. + +The Kubernetes provider can be configured using the `provider` field. For example, the `foo` field can be set to "bar": + +```yaml +$ cat << EOF > /etc/envoy-gateway/config.yaml +apiVersion: config.gateway.envoyproxy.io/v1alpha1 +kind: EnvoyGateway +provider: + type: Kubernetes + kubernetes: + foo: bar +EOF +``` + +__Note:__ The Provider API from the Kubernetes package is currently undefined and `foo: bar` is provided for +illustration purposes only. + +The same API structure is followed for each supported provider. The following example causes Envoy Gateway to use the +File provider: + +```yaml +$ cat << EOF > /etc/envoy-gateway/config.yaml +apiVersion: config.gateway.envoyproxy.io/v1alpha1 +kind: EnvoyGateway +provider: + type: File + file: + foo: bar +EOF +``` + +__Note:__ The Provider API from the File package is currently undefined and `foo: bar` is provided for illustration +purposes only. + +Gateway API-related configuration is expressed through the `gateway` field. If unspecified, Envoy Gateway will use +default configuration parameters for `gateway`. The following example causes the [GatewayClass][gc] controller to +manage GatewayClasses with controllerName `foo` instead of the default `gateway.envoyproxy.io/gatewayclass-controller`: + +```yaml +$ cat << EOF > /etc/envoy-gateway/config.yaml +apiVersion: config.gateway.envoyproxy.io/v1alpha1 +kind: EnvoyGateway +gateway: + controllerName: foo +``` + +With any of the above configuration examples, Envoy Gateway can be started without any additional arguments: + +```shell +$ ./envoy-gateway +``` + +## Data Plane API + +The data plane is configured dynamically through Kubernetes resources, primarily [Gateway API][gw_api] objects. +Optionally, the data plane infrastructure can be configured by referencing a [custom resource (CR)][cr] through +`spec.parametersRef` of the managed GatewayClass. The `EnvoyProxy` API defines the data plane infrastructure +configuration and is represented as the CR referenced by the managed GatewayClass. Key points of this API are: + +* If unreferenced by `gatewayclass.spec.parametersRef`, default parameters will be used to configure the data plane + infrastructure, e.g. expose Envoy network endpoints using a LoadBalancer service. +* Envoy Gateway will follow Gateway API [recommendations][gc] regarding updates to the EnvoyProxy CR: + > It is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the + > state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are + > not propagated down to existing Gateways. + +The initial `EnvoyProxy` API: + +```go +// gateway/api/config/v1alpha1/envoyproxy.go + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EnvoyProxy is the Schema for the envoyproxies API. +type EnvoyProxy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec EnvoyProxySpec `json:"spec,omitempty"` + Status EnvoyProxyStatus `json:"status,omitempty"` +} + +// EnvoyProxySpec defines the desired state of Envoy Proxy infrastructure +// configuration. +type EnvoyProxySpec struct { + // Undefined by this design spec. +} + +// EnvoyProxyStatus defines the observed state of EnvoyProxy. +type EnvoyProxyStatus struct { + // Undefined by this design spec. +} +``` + +The EnvoyProxySpec and EnvoyProxyStatus fields will be defined in the future as proxy infrastructure configuration use +cases are better understood. + +### Data Plane Configuration + +GatewayClass and Gateway resources define the data plane infrastructure. Note that all examples assume Envoy Gateway is +running with the Kubernetes provider. + +```yaml +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: GatewayClass +metadata: + name: example-class +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: example-gateway +spec: + gatewayClassName: example-class + listeners: + - name: http + protocol: HTTP + port: 80 +``` + +Since the GatewayClass does not define `spec.parametersRef`, the data plane is provisioned using default configuration +parameters. The Envoy proxies will be configured with a http listener and a Kubernetes LoadBalancer service listening +on port 80. + +The following example will configure the data plane to use a ClusterIP service instead of the default LoadBalancer +service: + +```yaml +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: GatewayClass +metadata: + name: example-class +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parametersRef: + name: example-config + group: config.gateway.envoyproxy.io + kind: EnvoyProxy +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: example-gateway +spec: + gatewayClassName: example-class + listeners: + - name: http + protocol: HTTP + port: 80 +--- +apiVersion: config.gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: example-config +spec: + networkPublishing: + type: ClusterIPService +``` + +__Note:__ The NetworkPublishing API is currently undefined and is provided here for illustration purposes only. + +[issue_51]: https://github.com/envoyproxy/gateway/issues/51 +[design_doc]: https://github.com/envoyproxy/gateway/blob/main/docs/design/SYSTEM_DESIGN.md +[gw_api]: https://gateway-api.sigs.k8s.io/ +[gc]: https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.GatewayClass +[cr]: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/ +[union]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#unions diff --git a/docs/v0.4/design/egctl.md b/docs/v0.4/design/egctl.md new file mode 100644 index 00000000000..7989ff49e5e --- /dev/null +++ b/docs/v0.4/design/egctl.md @@ -0,0 +1,57 @@ +# egctl Design + +## Motivation + +EG should provide a command line tool with following capabilities: + +- Collect configuration from envoy proxy and gateway +- Analyse system configuration to diagnose any issues in envoy gateway + +This tool is named `egctl`. + +## Syntax + +Use the following syntax to run `egctl` commands from your terminal window: + +```console +egctl [command] [entity] [name] [flags] +``` + +where `command`, `name`, and `flags` are: + +* `command`: Specifies the operation that you want to perform on one or more resources, + for example `config`, `version`. + +* `entity`: Specifies the entity the operation is being performed on such as `envoy-proxy` or `envoy-gateway`. + +* `name`: Specifies the name of the specified instance. + +* `flags`: Specifies optional flags. For example, you can use the `-c` or `--config` flags to specify the values for installing. + +If you need help, run `egctl help` from the terminal window. + +## Operation + +The following table includes short descriptions and the general syntax for all the `egctl` operations: + +| Operation | Syntax | Description | +| --------------| -------------------------------- | -------------------------------------------------------------------------------------| +| `version` | `egctl version` | Prints out build version information. | +| `config` | `egctl config ENTITY` | Retrieve information about proxy configuration from envoy proxy and gateway | +| `analyze` | `egctl analyze` | Analyze EG configuration and print validation messages | +| `experimental`| `egctl experimental` | Subcommand for experimental features. These do not guarantee backwards compatibility | + +## Examples + +Use the following set of examples to help you familiarize yourself with running the commonly used `egctl` operations: + +```console +# Retrieve all information about proxy configuration from envoy +egctl config envoy-proxy all + +# Retrieve listener information about proxy configuration from envoy +egctl config envoy-proxy listener + +# Retrieve information about envoy gateway +egctl config envoy-gateway +``` diff --git a/docs/v0.4/design/extending-envoy-gateway.md b/docs/v0.4/design/extending-envoy-gateway.md new file mode 100644 index 00000000000..61278025eb0 --- /dev/null +++ b/docs/v0.4/design/extending-envoy-gateway.md @@ -0,0 +1,324 @@ +# Envoy Gateway Extensions Design + +As outlined in the [official goals][] for the Envoy Gateway project, one of the main goals is to "provide a common foundation for vendors to build value-added products +without having to re-engineer fundamental interactions". Development of the Envoy Gateway project has been focused on developing the core features for the project and +Kubernetes Gateway API conformance. This system focuses on the “common foundation for vendors” component by introducing a way for vendors to extend Envoy Gateway. + +To meaningfully extend Envoy Gateway and provide additional features, Extensions need to be able to introduce their own custom resources and have a high level of control +over the configuration generated by Envoy Gateway. Simply applying some static xDS configuration patches or relying on the existing Gateway API resources are both insufficient on their own +as means to add larger features that require dynamic user-configuration. + +As an example, an extension developer may wish to provide their own out-of-the-box authentication filters that require configuration from the end-user. This is a scenario where the ability to introduce +custom resources and attach them to [HTTPRoute][]s as an [ExtensionRef][] is necessary. Providing the same feature through a series of xDS patch resources would be too cumbersome for many end-users that want to avoid +that level of complexity when managing their clusters. + +## Goals + +- Provide a foundation for extending the Envoy Gateway control plane +- Allow Extension Developers to introduce their own custom resources for extending the Gateway-API via [ExtensionRefs][], [policyAttachments][] (future) and [backendRefs][] (future). +- Extension developers should **NOT** have to maintain a custom fork of Envoy Gateway +- Provide a system for extending Envoy Gateway which allows extension projects to ship updates independent of Envoy Gateway's release schedule +- Modify the generated Envoy xDS config +- Setup a foundation for the initial iteration of Extending Envoy Gateway +- Allow an Extension to hook into the infra manager pipeline (future) + +## Non-Goals + +- The initial design does not capture every hook that Envoy Gateway will eventually support. +- Extend [Gateway API Policy Attachments][]. At some point, these will be addressed using this extension system, but the initial implementation omits these. +- Support multiple extensions at the same time. Due to the fact that extensions will be modifying xDS resources after they are generated, handling the order of extension execution for each individual hook point is a challenge. Additionally, there is no +real way to prevent one extension from overwriting or breaking modifications to xDS resources that were made by another extension that was executed first. + +## Overview + +Envoy Gateway can be extended by vendors by means of an extension server developed by the vendor and deployed alongside Envoy Gateway. +An extension server can make use of one or more pre/post hooks inside Envoy Gateway before and after its major components (translator, etc.) to allow the extension to modify the data going into or coming out of these components. +An extension can be created external to Envoy Gateway as its own Kubernetes deployment or loaded as a sidecar. gRPC is used for the calls between Envoy Gateway and an extension. In the hook call, Envoy Gateway sends data as well +as context information to the extension and expects a reply with a modified version of the data that was sent to the extension. Since extensions fundamentally alter the logic and data that Envoy Gateway provides, Extension projects assume responsibility for any bugs and issues +they create as a direct result of their modification of Envoy Gateway. + +## Diagram + +![Architecture](../images/extension-example.png) + +## Registering Extensions in Envoy Gateway + +Information about the extension that Envoy Gateway needs to load is configured in the Envoy Gateway config. + +An example configuration: + +```yaml +apiVersion: config.gateway.envoyproxy.io/v1alpha1 +kind: EnvoyGateway +extension: + resources: + - group: example.myextension.io + version: v2 + kind: OAuth2Filter + hooks: + post: + - Route + - VirtualHost + - HTTPListener + - Translation + service: + host: my-extension.example + port: 443 + tls: + certificateRef: + name: my-secret + namespace: default +``` + +An extension must supply connection information in the `extension.service` field so that Envoy Gateway can communicate with the extension. The `tls` configuration is optional. + +If the extension wants Envoy Gateway to watch resources for it then the extension must configure the optional `extension.resources` field and supply a list of: + +- `group`: the API group of the resource +- `version`: the API version of the resource +- `kind`: the Kind of resource + +The extension can configure the `extension.hooks` field to specify which hook points it would like to support. If a given hook is not listed here then it will not be executed even +if the extension is configured properly. This allows extension developers to only opt-in to the hook points they want to make use of. + +This configuration is required to be provided at bootstrap and modifying the registered extension during runtime is not currently supported. +Envoy Gateway will keep track of the registered extension and its API `groups` and `kinds` when processing Gateway API resources. + +## Extending Gateway API and the Data Plane + +Envoy Gateway manages [Envoy][] deployments, which act as the data plane that handles actual user traffic. Users configure the data plane using the K8s Gateway API resources which Envoy +Gateway converts into [Envoy specific configuration (xDS)][] to send over to Envoy. + +Gateway API offers [ExtensionRef filters][] and [Policy Attachments][] as extension points for implementers to use. Envoy Gateway extends the Gateway API using these extension points to provide support for [rate limiting][] +and [authentication][] native to the project. The initial design of Envoy Gateway extensions will primarily focus on ExtensionRef filters so that extension developers can reference their own resources as HTTP Filters in the same way +that Envoy Gateway has native support for rate limiting and authentication filters. + +When Envoy Gateway encounters an [HTTPRoute][] or [GRPCRoute][] that has an `ExtensionRef` `filter` with a `group` and `kind` that Envoy Gateway does not support, it will first +check the registered extension to determine if it supports the referenced object before considering it a configuration error. + +This allows users to be able to reference additional filters provided by their Envoy Gateway Extension, in their `HTTPRoute`s / `GRPCRoute`s: + +```yaml +apiVersion: example.myextension.io/v1alpha1 +kind: OAuth2Filter +metadata: + name: oauth2-filter +spec: + ... + +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: example +spec: + parentRefs: + - name: eg + hostnames: + - www.example.com + rules: + - clientSelectors: + - path: + type: PathPrefix + value: / + filters: + - type: ExtensionRef + extensionRef: + group: example.myextension.io + kind: OAuth2Filter + name: oauth2-filter + backendRefs: + - name: backend + port: 3000 +``` + +In order to enable the usage of new resources introduced by an extension for translation and xDS modification, Envoy Gateway provides hook points within the translation pipeline, where it calls out to the extension service registered in the [EnvoyGateway config][] +if they specify an `group` that matches the `group` of an `ExtensionRef` filter. The extension will then be able to modify the xDS that Envoy Gateway generated and send back the +modified configuration. If an extension is not registered or if the registered extension does not specify support for the `group` of an `ExtensionRef` filter then Envoy Gateway will treat it as an unknown resource +and provide an error to the user. + +**Note:** Currently (as of [v1beta1][]) Gateway API does not provide a means to specify the namespace or version of an object referenced as an `ExtensionRef`. The extension mechanism will assume that +the namespace of any `ExtensionRef` is the same as the namespace of the `HTTPRoute` or `GRPCRoute` it is attached to rather than treating the `name` field of an `ExtensionRef` as a `name.namespace` string. +If Gateway API adds support for these fields then the design of the Envoy Gateway extensions will be updated to support them. + +## Watching New Resources + +Envoy Gateway will dynamically create new watches on resources introduced by the registered Extension. It does so by using the [controller-runtime][] to create new watches on [Unstructured][] resources that match the `version`s, `group`s, and `kind`s that the +registered extension configured. When communicating with an extension, Envoy Gateway sends these Unstructured resources over to the extension. This eliminates the need for the extension to create its own watches which would have a strong chance of creating race conditions and reconciliation loops when resources change. When an extension receives the Unstructured resources from Envoy Gateway it can perform its own type validation on them. Currently we make the simplifying assumption that the registered extension's `Kinds` are filters referenced by `extensionRef` in `HTTPRouteFilter`s . Support for Policy attachments will be introduced at a later time. + +## xDS Hooks API + +Envoy Gateway supports the following hooks as the initial foundation of the Extension system. Additional hooks can be developed using this extension system at a later point as new use-cases and needs are discovered. The primary iteration of the extension hooks +focuses solely on the modification of xDS resources. + +### Route Modification Hook + +The [Route][] level Hook provides a way for extensions to modify a route generated by Envoy Gateway before it is finalized. +Doing so allows extensions to configure/modify route fields configured by Envoy Gateway and also to configure the +Route's TypedPerFilterConfig which may be desirable to do things such as pass settings and information to ext_authz filters. +The Post Route Modify hook also passes a list of Unstructured data for the externalRefs owned by the extension on the HTTPRoute that created this xDS route +This hook is always executed when an extension is loaded that has added `Route` to the `EnvoyProxy.extensions.hooks.post`, and only on Routes which were generated from an HTTPRoute that uses extension resources as externalRef filters. + +```go +// PostRouteModifyRequest sends a Route that was generated by Envoy Gateway along with context information to an extension so that the Route can be modified +message PostRouteModifyRequest { + envoy.config.route.v3.Route route = 1; + PostRouteExtensionContext post_route_context = 2; +} + +// RouteExtensionContext provides resources introduced by an extension and watched by Envoy Gateway +// additional context information can be added to this message as more use-cases are discovered +message PostRouteExtensionContext { + // Resources introduced by the extension that were used as extensionRefs in an HTTPRoute/GRPCRoute + repeated ExtensionResource extension_resources = 1; + + // hostnames are the fully qualified domain names attached to the HTTPRoute + repeated string hostnames = 2; +} + +// ExtensionResource stores the data for a K8s API object referenced in an HTTPRouteFilter +// extensionRef. It is constructed from an unstructured.Unstructured marshalled to JSON. An extension +// can marshal the bytes from this resource back into an unstructured.Unstructured and then +// perform type checking to obtain the resource. +message ExtensionResource { + bytes unstructured_bytes = 1; +} + +// PostRouteModifyResponse is the expected response from an extension and contains a modified version of the Route that was sent +// If an extension returns a nil Route then it will not be modified +message PostRouteModifyResponse { + envoy.config.route.v3.Route route = 1; +} +``` + +### VirtualHost Modification Hook + +The [VirtualHost][] Hook provides a way for extensions to modify a VirtualHost generated by Envoy Gateway before it is finalized. +An extension can also make use of this hook to generate and insert entirely new Routes not generated by Envoy Gateway. +This hook is always executed when an extension is loaded that has added `VirtualHost` to the `EnvoyProxy.extensions.hooks.post`. +An extension may return nil to not make any changes to the VirtualHost. + +```protobuf +// PostVirtualHostModifyRequest sends a VirtualHost that was generated by Envoy Gateway along with context information to an extension so that the VirtualHost can be modified +message PostVirtualHostModifyRequest { + envoy.config.route.v3.VirtualHost virtual_host = 1; + PostVirtualHostExtensionContext post_virtual_host_context = 2; +} + +// Empty for now but we can add fields to the context as use-cases are discovered without +// breaking any clients that use the API +// additional context information can be added to this message as more use-cases are discovered +message PostVirtualHostExtensionContext {} + +// PostVirtualHostModifyResponse is the expected response from an extension and contains a modified version of the VirtualHost that was sent +// If an extension returns a nil Virtual Host then it will not be modified +message PostVirtualHostModifyResponse { + envoy.config.route.v3.VirtualHost virtual_host = 1; +} +``` + +### HTTP Listener Modification Hook + +The HTTP [Listener][] modification hook is the broadest xDS modification Hook available and allows an extension to make changes to a Listener generated by Envoy Gateway before it is finalized. +This hook is always executed when an extension is loaded that has added `HTTPListener` to the `EnvoyProxy.extensions.hooks.post`. An extension may return nil +in order to not make any changes to the Listener. + +```protobuf +// PostVirtualHostModifyRequest sends a Listener that was generated by Envoy Gateway along with context information to an extension so that the Listener can be modified +message PostHTTPListenerModifyRequest { + envoy.config.listener.v3.Listener listener = 1; + PostHTTPListenerExtensionContext post_listener_context = 2; +} + +// Empty for now but we can add fields to the context as use-cases are discovered without +// breaking any clients that use the API +// additional context information can be added to this message as more use-cases are discovered +message PostHTTPListenerExtensionContext {} + +// PostHTTPListenerModifyResponse is the expected response from an extension and contains a modified version of the Listener that was sent +// If an extension returns a nil Listener then it will not be modified +message PostHTTPListenerModifyResponse { + envoy.config.listener.v3.Listener listener = 1; +} +``` + +### Post xDS Translation Modify Hook + +The Post Translate Modify hook allows an extension to modify the clusters and secrets in the xDS config. +This allows for inserting clusters that may change along with extension specific configuration to be dynamically created rather than +using custom bootstrap config which would be sufficient for clusters that are static and not prone to have their configurations changed. +An example of how this may be used is to inject a cluster that will be used by an ext_authz http filter created by the extension. +The list of clusters and secrets returned by the extension are used as the final list of all clusters and secrets +This hook is always executed when an extension is loaded that has added `Translation` to the `EnvoyProxy.extensions.hooks.post`. + +```protobuf +// PostTranslateModifyRequest currently sends only clusters and secrets to an extension. +// The extension is free to add/modify/remove the resources it received. +message PostTranslateModifyRequest { + PostTranslateExtensionContext post_translate_context = 1; + repeated envoy.config.cluster.v3.Cluster clusters = 2; + repeated envoy.extensions.transport_sockets.tls.v3.Secret secrets = 3; +} + +// PostTranslateModifyResponse is the expected response from an extension and contains +// the full list of xDS clusters and secrets to be used for the xDS config. +message PostTranslateModifyResponse { + repeated envoy.config.cluster.v3.Cluster clusters = 1; + repeated envoy.extensions.transport_sockets.tls.v3.Secret secrets = 2; +} +``` + +### Extension Service + +Currently, an extension must implement all of the following hooks although it may return the input(s) it received +if no modification of the resource is desired. A future expansion of the extension hooks will allow an Extension to specify +with config which Hooks it would like to "subscribe" to and which Hooks it does not wish to support. These specific Hooks were chosen +in order to provide extensions with the ability to have both broad and specific control over xDS resources and to minimize the amount of data being sent. + +```protobuf +service EnvoyGatewayExtension { + rpc PostRouteModify (PostRouteModifyRequest) returns (PostRouteModifyResponse) {}; + rpc PostVirtualHostModify(PostVirtualHostModifyRequest) returns (PostVirtualHostModifyResponse) {}; + rpc PostHTTPListenerModify(PostHTTPListenerModifyRequest) returns (PostHTTPListenerModifyResponse) {}; + rpc PostTranslateModify(PostTranslateModifyRequest) returns (PostTranslateModifyResponse) {}; +} +``` + +## Design Decisions + +- Envoy Gateway watches new custom resources introduced by a loaded extension and passes the resources back to the extension when they are used. + - This decision was made to solve the problem about how resources introduced by an extension get watched. If an extension server watches its own resources then it would need some way to trigger an Envoy Gateway reconfigure when a resource that Envoy Gateway is not watching gets updated. Having Envoy Gateway watch all resources removes any concern about creating race confitions or reconcile loops that would result from Envoy Gateway and the extension server both having so much separate state that needs to be synchronized. +- The Extension Server takes ownership of producing the correct xDS configuration in the hook responses +- The Extension Server will be responsible for ensuring the performance of the hook processing time +- The Post xDS level gRPC hooks all currently send a context field even though it contains nothing for several hooks. These fields exist so that they can be updadated in the future to pass +additional information to extensions as new use-cases and needs are discovered. +- The initial design supplies the scaffolding for both "pre xDS" and "post xDS" hooks. Only the post hooks are currently implemented which operate on xDS resources after they have been generated. +The pre hooks will be implemented at a later date along with one or more hooks in the infra manager. The infra manager level hook(s) will exist to power use-cases such as dynamically creating Deployments/Services for the extension the +whenever Envoy Gateway creates an instance of Envoy Proxy. An extension developer might want to take advantage of this functionality to inject a new authorization service as a sidecar on the Envoy Proxy deployment for reduced latency. +- Multiple extensions are not be supported at the same time. Preventing conflict between multiple extensions that are mangling xDS resources is too difficult to ensure compatibility with and is likely to only generate issues. + +## Known Challenges + +Extending Envoy Gateway by using an external extension server which makes use of hook points in Envoy Gateway does comes with a few trade-offs. One known trade-off is the impact of the time that it takes for the hook calls to be executed. Since an extension would make use of hook points in Envoy Gateway that use gRPC for communication, the time it takes to perform these requests could become a concern for some extension developers. One way to minimize the request time of the hook calls is to load the extension server as a sidecar to Envoy Gateway to minimize the impact of networking on the hook calls. + +[official goals]: https://github.com/envoyproxy/gateway/blob/main/GOALS.md#extensibility +[ExtensionRef filters]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.LocalObjectReference +[ExtensionRef]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.LocalObjectReference +[ExtensionRefs]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.LocalObjectReference +[backendRefs]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.BackendObjectReference +[Gateway API Policy attachments]: https://gateway-api.sigs.k8s.io/references/policy-attachment/?h=policy +[Policy Attachments]: https://gateway-api.sigs.k8s.io/references/policy-attachment/?h=policy +[policyAttachments]: https://gateway-api.sigs.k8s.io/references/policy-attachment/?h=policy +[Envoy]: https://www.envoyproxy.io/ +[Envoy specific configuration (xDS)]: https://www.envoyproxy.io/docs/envoy/v1.25.1/configuration/configuration +[v1beta1]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io%2fv1beta1 +[rate limiting]: https://gateway.envoyproxy.io/v0.3.0/user/rate-limit.html +[authentication]: https://gateway.envoyproxy.io/v0.3.0/user/authn.html +[HTTPRoute]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRoute +[GRPCRoute]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute +[EnvoyGateway config]: https://gateway.envoyproxy.io/v0.3.0/api/config_types.html#envoygateway +[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime +[Unstructured]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured +[Listener]: https://www.envoyproxy.io/docs/envoy/v1.23.0/api-v3/config/listener/v3/listener.proto#config-listener-v3-listener +[VirtualHost]: https://www.envoyproxy.io/docs/envoy/v1.23.0/api-v3/config/route/v3/route_components.proto#config-route-v3-virtualhost +[Route]: https://www.envoyproxy.io/docs/envoy/v1.23.0/api-v3/config/route/v3/route_components.proto#config-route-v3-route diff --git a/docs/v0.4/design/gatewayapi-support.md b/docs/v0.4/design/gatewayapi-support.md new file mode 100644 index 00000000000..4ce9f1b041e --- /dev/null +++ b/docs/v0.4/design/gatewayapi-support.md @@ -0,0 +1,119 @@ +# Gateway API Support + +As mentioned in the [system design][] document, Envoy Gateway's managed data plane is configured dynamically through +Kubernetes resources, primarily [Gateway API][] objects. Envoy Gateway supports configuration using the following Gateway API resources. + +## GatewayClass + +A [GatewayClass][] represents a "class" of gateways, i.e. which Gateways should be managed by Envoy Gateway. +Envoy Gateway supports managing __a single__ GatewayClass resource that matches its configured `controllerName` and +follows Gateway API guidelines for [resolving conflicts][] when multiple GatewayClasses exist with a matching +`controllerName`. + +__Note:__ If specifying GatewayClass [parameters reference][], it must refer to an [EnvoyProxy][] resource. + +## Gateway + +When a [Gateway][] resource is created that references the managed GatewayClass, Envoy Gateway will create and manage a +new Envoy Proxy deployment. Gateway API resources that reference this Gateway will configure this managed Envoy Proxy +deployment. + +__Note:__ Envoy Gateway does not support multiple certificate references or specifying an [address][] for the Gateway. + +## HTTPRoute + +An [HTTPRoute][] configures routing of HTTP traffic through one or more Gateways. The following HTTPRoute filters are +supported by Envoy Gateway: + +- `requestHeaderModifier`: [RequestHeaderModifiers](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilter) + can be used to modify or add request headers before the request is proxied to its destination. +- `responseHeaderModifier`: [ResponseHeaderModifiers](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilter) + can be used to modify or add response headers before the response is sent back to the client. +- `requestMirror`: [RequestMirrors](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilter) + configure destinations where the requests should also be mirrored to. Responses to mirrored requests will be ignored. +- `requestRedirect`: [RequestRedirects](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilter) + configure policied for how requests that match the HTTPRoute should be modified and then redirected. +- `urlRewrite`: [UrlRewrites](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilter) + allow for modification of the request's hostname and path before it is proxied to its destination. +- `extensionRef`: [ExtensionRefs][] are used by Envoy Gateway to implement extended filters. Currently, Envoy Gateway + supports rate limiting and request authentication filters. For more information about these filters, refer to the + [rate limiting][] and [request authentication][] documentation. + +__Notes:__ +- The only [BackendRef][] kind supported by Envoy Gateway is a [Service][]. Routing traffic to other destinations such + as arbitrary URLs is not possible. +- The `filters` field within [HTTPBackendRef][] is not supported. + +## TCPRoute + +A [TCPRoute][] configures routing of raw TCP traffic through one or more Gateways. Traffic can be forwarded to the +desired BackendRefs based on a TCP port number. + +__Note:__ A TCPRoute only supports proxying in non-transparent mode, i.e. the backend will see the source IP and port of +the Envoy Proxy instance instead of the client. + +## UDPRoute + +A [UDPRoute][] configures routing of raw UDP traffic through one or more Gateways. Traffic can be forwarded to the +desired BackendRefs based on a UDP port number. + +__Note:__ Similar to TCPRoutes, UDPRoutes only support proxying in non-transparent mode i.e. the backend will see the +source IP and port of the Envoy Proxy instance instead of the client. + +## GRPCRoute + +A [GRPCRoute][] configures routing of [gRPC][] requests through one or more Gateways. They offer request matching by +hostname, gRPC service, gRPC method, or HTTP/2 Header. Envoy Gateway supports the following filters on GRPCRoutes to +provide additional traffic processing: + +- `requestHeaderModifier`: [RequestHeaderModifiers](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRouteFilter) + can be used to modify or add request headers before the request is proxied to its destination. +- `responseHeaderModifier`: [ResponseHeaderModifiers](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRouteFilter) + can be used to modify or add response headers before the response is sent back to the client. +- `requestMirror`: [RequestMirrors](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRouteFilter) + configure destinations where the requests should also be mirrored to. Responses to mirrored requests will be ignored. + +__Notes:__ +- The only [BackendRef][grpc-filter] kind supported by Envoy Gateway is a [Service][]. Routing traffic to other + destinations such as arbitrary URLs is not currently possible. +- The `filters` field within [HTTPBackendRef][] is not supported. + +## TLSRoute + +A [TLSRoute][] configures routing of TCP traffic through one or more Gateways. However, unlike TCPRoutes, TLSRoutes +can match against TLS-specific metadata. + +## ReferenceGrant + +A [ReferenceGrant][] is used to allow a resource to reference another resource in a different namespace. Normally an +HTTPRoute created in namespace `foo` is not allowed to reference a Service in namespace `bar`. A ReferenceGrant permits +these types of cross-namespace references. Envoy Gateway supports the following ReferenceGrant use-cases: + +- Allowing an HTTPRoute, GRPCRoute, TLSRoute, UDPRoute, or TCPRoute to reference a Service in a different namespace. +- Allowing an HTTPRoute's `requestMirror` filter to include a BackendRef that references a Service in a different + namespace. +- Allowing a Gateway's [SecretObjectReference][] to reference a secret in a different namespace. + +[system design]: https://gateway.envoyproxy.io/latest/design/system-design.html +[Gateway API]: https://gateway-api.sigs.k8s.io/ +[GatewayClass]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.GatewayClass +[parameters reference]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.ParametersReference +[Gateway]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.Gateway +[address]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.GatewayAddress +[HTTPRoute]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRoute +[Service]: https://kubernetes.io/docs/concepts/services-networking/service/ +[BackendRef]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.BackendRef +[HTTPBackendRef]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPBackendRef +[TCPRoute]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute +[UDPRoute]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.UDPRoute +[GRPCRoute]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute +[gRPC]: https://grpc.io/ +[TLSRoute]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.TLSRoute +[ReferenceGrant]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io%2fv1beta1.ReferenceGrant +[SecretObjectReference]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.SecretObjectReference +[rate limiting]: https://gateway.envoyproxy.io/latest/user/rate-limit.html +[request authentication]: https://gateway.envoyproxy.io/latest/user/authn.html +[EnvoyProxy]: https://gateway.envoyproxy.io/latest/api/config_types.html#envoyproxy +[resolving conflicts]: https://gateway-api.sigs.k8s.io/concepts/guidelines/?h=conflict#conflicts +[ExtensionRefs]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilterType +[grpc-filter]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRouteFilter diff --git a/docs/v0.4/design/gatewayapi-translator.md b/docs/v0.4/design/gatewayapi-translator.md new file mode 100644 index 00000000000..3cf0da94f5a --- /dev/null +++ b/docs/v0.4/design/gatewayapi-translator.md @@ -0,0 +1,250 @@ +# Gateway API Translator Design + +The Gateway API translates external resources, e.g. GatewayClass, from the configured Provider to the Intermediate +Representation (IR). + +## Assumptions + +Initially target core conformance features only, to be followed by extended conformance features. + +## Inputs and Outputs + +The main inputs to the Gateway API translator are: + +- GatewayClass, Gateway, HTTPRoute, TLSRoute, Service, ReferenceGrant, Namespace, and Secret resources. + +__Note:__ ReferenceGrant is not fully implemented as of v0.2. + +The outputs of the Gateway API translator are: + +- Xds and Infra Internal Representations (IRs). +- Status updates for GatewayClass, Gateways, HTTPRoutes + +## Listener Compatibility + +Envoy Gateway follows Gateway API listener compatibility spec: +> Each listener in a Gateway must have a unique combination of Hostname, Port, and Protocol. An implementation MAY group +> Listeners by Port and then collapse each group of Listeners into a single Listener if the implementation determines +> that the Listeners in the group are “compatible”. + +__Note:__ Envoy Gateway does not collapse listeners across multiple Gateways. + +### Listener Compatibility Examples + +#### Example 1: Gateway with compatible Listeners (same port & protocol, different hostnames) + +```yaml +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1beta1 +metadata: + name: gateway-1 + namespace: envoy-gateway +spec: + gatewayClassName: envoy-gateway + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + hostname: "*.envoygateway.io" + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + hostname: whales.envoygateway.io +``` + +#### Example 2: Gateway with compatible Listeners (same port & protocol, one hostname specified, one not) + +```yaml +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1beta1 +metadata: + name: gateway-1 + namespace: envoy-gateway +spec: + gatewayClassName: envoy-gateway + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + hostname: "*.envoygateway.io" + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +``` + +#### Example 3: Gateway with incompatible Listeners (same port, protocol and hostname) + +```yaml +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1beta1 +metadata: + name: gateway-1 + namespace: envoy-gateway +spec: + gatewayClassName: envoy-gateway + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + hostname: whales.envoygateway.io + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + hostname: whales.envoygateway.io +``` + +#### Example 4: Gateway with incompatible Listeners (neither specify a hostname) + +```yaml +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1beta1 +metadata: + name: gateway-1 + namespace: envoy-gateway +spec: + gatewayClassName: envoy-gateway + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +``` + +## Computing Status + +Gateway API specifies a rich set of status fields & conditions for each resource. To achieve conformance, Envoy Gateway +must compute the appropriate status fields and conditions for managed resources. + +Status is computed and set for: + +- The managed GatewayClass (`gatewayclass.status.conditions`). +- Each managed Gateway, based on its Listeners' status (`gateway.status.conditions`). For the Kubernetes provider, the + Envoy Deployment and Service status are also included to calculate Gateway status. +- Listeners for each Gateway (`gateway.status.listeners`). +- The ParentRef for each Route (`route.status.parents`). + +The Gateway API translator is responsible for calculating status conditions while translating Gateway API resources to +the IR and publishing status over the [message bus][]. The Status Manager subscribes to these status messages and +updates the resource status using the configured provider. For example, the Status Manager uses a Kubernetes client to +update resource status on the Kubernetes API server. + +## Outline + +The following roughly outlines the translation process. Each step may produce (1) IR; and (2) status updates on Gateway +API resources. + +1. Process Gateway Listeners + - Validate unique hostnames, ports, and protocols. + - Validate and compute supported kinds. + - Validate allowed namespaces (validate selector if specified). + - Validate TLS fields if specified, including resolving referenced Secrets. + +2. Process HTTPRoutes + - foreach route rule: + - compute matches + - [core] path exact, path prefix + - [core] header exact + - [extended] query param exact + - [extended] HTTP method + - compute filters + - [core] request header modifier (set/add/remove) + - [core] request redirect (hostname, statuscode) + - [extended] request mirror + - compute backends + - [core] Kubernetes services + - foreach route parent ref: + - get matching listeners (check Gateway, section name, listener validation status, listener allowed routes, hostname intersection) + - foreach matching listener: + - foreach hostname intersection with route: + - add each computed route rule to host + +## Context Structs + +To help store, access and manipulate information as it's processed during the translation process, a set of context +structs are used. These structs wrap a given Gateway API type, and add additional fields and methods to support +processing. + +`GatewayContext` wraps a Gateway and provides helper methods for setting conditions, accessing Listeners, etc. + +```go +type GatewayContext struct { + // The managed Gateway + *v1beta1.Gateway + + // A list of Gateway ListenerContexts. + listeners []*ListenerContext +} +``` + +`ListenerContext` wraps a Listener and provides helper methods for setting conditions and other status information on +the associated Gateway. + +```go +type ListenerContext struct { + // The Gateway listener. + *v1beta1.Listener + + // The Gateway this Listener belongs to. + gateway *v1beta1.Gateway + + // An index used for managing this listener in the list of Gateway listeners. + listenerStatusIdx int + + // Only Routes in namespaces selected by the selector may be attached + // to the Gateway this listener belongs to. + namespaceSelector labels.Selector + + // The TLS Secret for this Listener, if applicable. + tlsSecret *v1.Secret +} +``` + +`RouteContext` represents a generic Route object (HTTPRoute, TLSRoute, etc.) that can reference Gateway objects. + +```go +type RouteContext interface { + client.Object + + // GetRouteType returns the Kind of the Route object, HTTPRoute, + // TLSRoute, TCPRoute, UDPRoute etc. + GetRouteType() string + + // GetHostnames returns the hosts targeted by the Route object. + GetHostnames() []string + + // GetParentReferences returns the ParentReference of the Route object. + GetParentReferences() []v1beta1.ParentReference + + // GetRouteParentContext returns RouteParentContext by using the Route + // objects' ParentReference. + GetRouteParentContext(forParentRef v1beta1.ParentReference) *RouteParentContext +} +``` + +[message bus]: watching.md diff --git a/docs/v0.4/design/rate-limit.md b/docs/v0.4/design/rate-limit.md new file mode 100644 index 00000000000..b3e88e2ae99 --- /dev/null +++ b/docs/v0.4/design/rate-limit.md @@ -0,0 +1,346 @@ +# Rate Limit Design + +## Overview + +Rate limit is a feature that allows the user to limit the number of incoming requests +to a predefined value based on attributes within the traffic flow. + +Here are some reasons why a user may want to implements Rate limits + +* To prevent malicious activity such as DDoS attacks. +* To prevent applications and its resources (such as a database) from getting overloaded. +* To create API limits based on user entitlements. + +## Scope Types + +The rate limit type here describes the scope of rate limits. + +* Global - In this case, the rate limit is common across all the instances of Envoy proxies +where its applied i.e. if the data plane has 2 replicas of Envoy running, and the rate limit is +10 requests/second, this limit is common and will be hit if 5 requests pass through the first replica +and 5 requests pass through the second replica within the same second. + +* Local - In this case, the rate limits are specific to each instance/replica of Envoy running. +Note - This is not part of the initial design and will be added as a future enhancement. + +## Match Types + +### Rate limit a specific traffic flow + +* Here is an example of a ratelimit implemented by the application developer to limit a specific user +by matching on a custom `x-user-id` header with a value set to `one` + +```yaml +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: RateLimitFilter +metadata: + name: ratelimit-specific-user +spec: + type: Global + global: + rules: + - clientSelectors: + - headers: + - name: x-user-id + value: one + limit: + requests: 10 + unit: Hour +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: example +spec: + parentRefs: + - name: eg + hostnames: + - www.example.com + rules: + - matches: + - path: + type: PathPrefix + value: /foo + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: RateLimitFilter + name: ratelimit-specific-user + backendRefs: + - name: backend + port: 3000 +``` + +### Rate limit all traffic flows + +* Here is an example of a rate limit implemented by the application developer that limits the total requests made +to a specific route to safeguard health of internal application components. In this case, no specific `headers` match +is specified, and the rate limit is applied to all traffic flows accepted by this `HTTPRoute`. + +```yaml +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: RateLimitFilter +metadata: + name: ratelimit-all-requests +spec: + type: Global + global: + rules: + - limit: + requests: 1000 + unit: Second +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: example +spec: + parentRefs: + - name: eg + hostnames: + - www.example.com + rules: + - matches: + - path: + type: PathPrefix + value: /foo + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: RateLimitFilter + name: ratelimit-all-requests + backendRefs: + - name: backend + port: 3000 +``` + +### Rate limit per distinct value + +* Here is an example of a rate limit implemented by the application developer to limit any unique user +by matching on a custom `x-user-id` header. Here, user A (recognised from the traffic flow using the header +`x-user-id` and value `a`) will be rate limited at 10 requests/hour and so will user B +(recognised from the traffic flow using the header `x-user-id` and value `b`). + +```yaml +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: RateLimitFilter +metadata: + name: ratelimit-per-user +spec: + type: Global + global: + rules: + - clientSelectors: + - headers: + - type: Distinct + name: x-user-id + limit: + requests: 10 + unit: Hour +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: example +spec: + parentRefs: + - name: eg + hostnames: + - www.example.com + rules: + - matches: + - path: + type: PathPrefix + value: /foo + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: RateLimitFilter + name: ratelimit-per-user + backendRefs: + - name: backend + port: 3000 +``` + +### Rate limit per source IP + +* Here is an example of a rate limit implemented by the application developer that limits the total requests made +to a specific route by matching on source IP. In this case, requests from `x.x.x.x` will be rate limited at 10 requests/hour. + +```yaml +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: RateLimitFilter +metadata: + name: ratelimit-per-ip +spec: + type: Global + global: + rules: + - clientSelectors: + - sourceIP: x.x.x.x/32 + limit: + requests: 10 + unit: Hour +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: example +spec: + parentRefs: + - name: eg + hostnames: + - www.example.com + rules: + - matches: + - path: + type: PathPrefix + value: /foo + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: RateLimitFilter + name: ratelimit-per-user + backendRefs: + - name: backend + port: 3000 +``` + +## Multiple RateLimitFilters, rules and clientSelectors +* Users can create multiple `RateLimitFilter`s and apply it to the same `HTTPRoute`. In such a case each +`RateLimitFilter` will be applied to the route and matched (and limited) in a mutually exclusive way, independent of each other. +* Rate limits are applied for each `RateLimitFilter` `rule` when ALL the conditions under `clientSelectors` hold true. + +Here's an example highlighting this - + +```yaml +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: RateLimitFilter +metadata: + name: ratelimit-all-safeguard-app +spec: + type: Global + global: + rules: + - limit: + requests: 100 + unit: Second +--- + +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: RateLimitFilter +metadata: + name: ratelimit-per-user +spec: + type: Global + global: + rules: + - clientSelectors: + - headers: + - type: Distinct + name: x-user-id + limit: + requests: 1000 + unit: Hour +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: example +spec: + parentRefs: + - name: eg + hostnames: + - www.example.com + rules: + - matches: + - path: + type: PathPrefix + value: /foo + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: RateLimitFilter + name: ratelimit-per-user + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: RateLimitFilter + name: ratelimit-all-safeguard-app + backendRefs: + - name: backend + port: 3000 +``` + +* The user has created two `RateLimitFilter`s and has attached it to a `HTTPRoute` - one(`ratelimit-all-safeguard-app`) to +ensure that the backend does not get overwhelmed with requests, any excess requests are rate limited irrespective of +the attributes within the traffic flow, and another(`ratelimit-per-user`) to rate limit each distinct user client +who can be differentiated using the `x-user-id` header, to ensure that each client does not make exessive requests to the backend. +* If user `baz` (identified with the header and value of `x-user-id: baz`) sends 90 requests within the first second, and +user `bar` sends 11 more requests during that same interval of 1 second, and user `bar` sends the 101th request within that second, +the rule defined in `ratelimit-all-safeguard-app` gets activated and Envoy Gateway will ratelimit the request sent by `bar` (and any other +request sent within that 1 second). After 1 second, the rate limit counter associated with the `ratelimit-all-safeguard-app` rule +is reset and again evaluated. +* If user `bar` also ends up sending 90 more requests within the hour, summing up `bar`'s total request count to 101, the rate limit rule +defined within `ratelimit-per-user` will get activated, and `bar`'s requests will be rate limited again until the hour interval ends. +* Within the same above hour, if `baz` sends 991 more requests, summing up `baz`'s total request count to 1001, the rate limit rule defined +within `ratelimit-per-user` will get activated for `baz`, and `baz`'s requests will also be rate limited until the hour interval ends. + +## Design Decisions + +* The initial design uses an Extension filter to apply the Rate Limit functionality on a specific [HTTPRoute][]. +This was preferred over the [PolicyAttachment][] extension mechanism, because it is unclear whether Rate Limit +will be required to be enforced or overridden by the platform administrator or not. +* The RateLimitFilter can only be applied as a filter to a [HTTPRouteRule[], applying it across all backends within a [HTTPRoute][] +and cannot be applied a filter within a [HTTPBackendRef][] for a specific backend. +* The [HTTPRoute][] API has a [matches][] field within each [rule][] to select a specific traffic flow to be routed to +the destination backend. The RateLimitFilter API that can be attached to an HTTPRoute via an [extensionRef][] filter, +also has a `clientSelectors` field within each `rule` to select attributes within the traffic flow to rate limit specific clients. +The two levels of selectors/matches allow for flexibility and aim to hold match information specific to its use, allowing the author/owner +of each configuration to be different. It also allows the `clientSelectors` field within the RateLimitFilter to be enhanced with other matchable +attribute such as [IP subnet][] in the future that are not relevant in the [HTTPRoute][] API. + +## Implementation Details + +### Global Rate limiting + +* [Global rate limiting][] in Envoy Proxy can be achieved using the following - + * [Actions][] can be conifgured per [xDS Route][]. + * If the match criteria defined within these actions is met for a specific HTTP Request, a set of key value pairs called [descriptors][] + defined within the above actions is sent to a remote [rate limit service][], whose configuration (such as the URL for the rate limit service) is defined + using a [rate limit filter][]. + * Based on information received by the rate limit service and its programmed configuration, a decision is computed, whether to rate limit + the HTTP Request or not, and is sent back to Envoy, which enforces this decision on the data plane. +* Envoy Gateway will leverage this Envoy Proxy feature by - + * Translating the user facing RateLimitFilter API into Rate limit [Actions][] as well as Rate limit service configuration to implement + the desired API intent. + * Envoy Gateway will use the existing [reference implementation][] of the rate limit service. + * The Infrastructure administrator will need to enable the rate limit service using new settings that will be defined in the [EnvoyGateway][] config API. + * The xDS IR will be enhanced to hold the user facing rate limit intent. + * The xDS Translator will be enhanced to translate the rate limit field within the xDS IR into Rate limit [Actions][] as well as instantiate the [rate limit filter][]. + * A new runner called `rate-limit` will be added that subscribes to the xDS IR messages and translates it into a new Rate Limit Infra IR which contains + the [rate limit service configuration][] as well as other information needed to deploy the rate limit service. + * The infrastructure service will be enhanced to subscribe to the Rate Limit Infra IR and deploy a provider specific rate limit service runnable entity. + * A Status field within the RateLimitFilter API will be added to reflect whether the specific configuration was programmed correctly in these multiple locations or not. + +[PolicyAttachment]: https://gateway-api.sigs.k8s.io/references/policy-attachment/ +[HTTPRoute]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRoute +[HTTPBackendRef]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io%2fv1beta1.HTTPBackendRef +[matches]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteMatch +[rule]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteMatch +[extensionRef]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilterType +[IP subnet]: https://en.wikipedia.org/wiki/Subnetwork +[Actions]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-msg-config-route-v3-ratelimit-action +[descriptors]: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/rate_limit_filter.html?highlight=descriptor#example-1 +[Global rate limiting]: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/other_features/global_rate_limiting +[xDS Route]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-msg-config-route-v3-routeaction +[rate limit filter]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ratelimit/v3/rate_limit.proto#envoy-v3-api-msg-extensions-filters-http-ratelimit-v3-ratelimit +[rate limit service]: https://www.envoyproxy.io/docs/envoy/latest/configuration/other_features/rate_limit#config-rate-limit-service +[reference implementation]: https://github.com/envoyproxy/ratelimit +[EnvoyGateway]: https://github.com/envoyproxy/gateway/blob/main/api/config/v1alpha1/envoygateway_types.go +[rate limit service configuration]: https://github.com/envoyproxy/ratelimit#configuration diff --git a/docs/v0.4/design/request-authentication.md b/docs/v0.4/design/request-authentication.md new file mode 100644 index 00000000000..7f75fa07291 --- /dev/null +++ b/docs/v0.4/design/request-authentication.md @@ -0,0 +1,513 @@ +# Request Authentication Design + +## Overview + +[Issue 336][] specifies the need for exposing a user-facing API to configure request authentication. Request +authentication is defined as an authentication mechanism to be enforced by Envoy on a per-request basis. A connection +will be rejected if it contains invalid authentication information, based on the `AuthenticationFilter` API type +proposed in this design document. + +Envoy Gateway leverages [Gateway API][] for configuring managed Envoy proxies. Gateway API defines core, extended, and +implementation-specific API [support levels][] for implementors such as Envoy Gateway to expose features. Since +implementing request authentication is not covered by `Core` or `Extended` APIs, an `Implementation-specific` API will +be created for this purpose. + +## Goals + +* Define an API for configuring request authentication. +* Implement [JWT] as the first supported authentication type. +* Allow users that manage routes, e.g. [HTTPRoute][], to authenticate matching requests before forwarding to a backend + service. +* Support HTTPRoutes as an Authentication API referent. HTTPRoute provides multiple [extension points][]. The + [HTTPRouteFilter][] is the extension point supported by the Authentication API. + +## Non-Goals + +* Allow infrastructure administrators to override or establish default authentication policies. +* Support referents other than HTTPRoute. +* Support Gateway API extension points other than HTTPRouteFilter. + +## Use-Cases + +These use-cases are presented as an aid for how users may attempt to utilize the outputs of the design. They are not an +exhaustive list of features for authentication support in Envoy Gateway. + +As a Service Producer, I need the ability to: +* Authenticate a request before forwarding it to a backend service. +* Have different authentication mechanisms per route rule. +* Choose from different authentication mechanisms supported by Envoy, e.g. OIDC. + +### Authentication API Type + +The Authentication API type defines authentication configuration for authenticating requests through managed Envoy +proxies. + +```go +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +) + +type AuthenticationFilter struct { + metav1.TypeMeta + metav1.ObjectMeta + + // Spec defines the desired state of the Authentication type. + Spec AuthenticationFilterSpec + + // Note: The status sub-resource has been excluded but may be added in the future. +} + +// AuthenticationFilterSpec defines the desired state of the AuthenticationFilter type. +// +union +type AuthenticationFilterSpec struct { + // Type defines the type of authentication provider to use. Supported provider types are: + // + // * JWT: A provider that uses JSON Web Token (JWT) for authenticating requests. + // + // +unionDiscriminator + Type AuthenticationFilterType + + // JWT defines the JSON Web Token (JWT) authentication provider type. When multiple + // jwtProviders are specified, the JWT is considered valid if any of the providers + // successfully validate the JWT. + JwtProviders []JwtAuthenticationFilterProvider +} + +... +``` + +Refer to [PR 773][] for the detailed AuthenticationFilter API spec. + +The status subresource is not included in the AuthenticationFilter API. Status will be surfaced by an HTTPRoute that +references an AuthenticationFilter. For example, an HTTPRoute will surface the `ResolvedRefs=False` status condition if it +references an AuthenticationFilter that does not exist. It may be beneficial to add AuthenticationFilter status fields in the future +based on defined use-cases. For example, a remote [JWKS][] can be validated based on the specified URI and have an +appropriate status condition surfaced. + +#### AuthenticationFilter Example + +The following is an AuthenticationFilter example with one JWT authentication provider: + +```yaml +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: AuthenticationFilter +metadata: + name: example +spec: + type: JWT + jwtProviders: + - name: example + issuer: https://www.example.com + audiences: + - foo.com + remoteJwks: + uri: https://foo.com/jwt/public-key/jwks.json + +``` + +__Note:__ `type` is a union type, allowing only one of any supported provider type such as `jwtProviders` to be +specified. + +The following is an example HTTPRoute configured to use the above JWT authentication provider: + +```yaml +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: example +spec: + parentRefs: + - name: eg + hostnames: + - www.example.com + rules: + - matches: + - path: + type: PathPrefix + value: /foo + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: AuthenticationFilter + name: example + backendRefs: + - name: backend + port: 3000 +``` + +Requests for `www.example.com/foo` will be authenticated using the referenced JWT provider before being forwarded to the +backend service named "backend". + +## Implementation Details + +The JWT authentication type is translated to an Envoy [JWT authentication filter][] and a cluster is created for each +remote [JWKS][]. The following examples provide additional details on how Gateway API and AuthenticationFilter resources are +translated into Envoy configuration. + +### Example 1: One Route with One JWT Provider + +The following cluster is created from the above HTTPRoute and AuthenticationFilter: + +```yaml +dynamic_clusters: + - name: foo.com|443 + load_assignment: + cluster_name: foo.com|443 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: foo.com + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + sni: foo.com + common_tls_context: + validation_context: + match_subject_alt_names: + - exact: "*.foo.com" + trusted_ca: + filename: /etc/ssl/certs/ca-certificates.crt +``` + +A JWT authentication HTTP filter is added to the HTTP Connection Manager. For example: + +```yaml +dynamic_resources: + dynamic_listeners: + - name: example_listener + address: + socket_address: + address: 1.2.3.4 + port_value: 80 + filter_chains: + - filters: + - name: envoy.http_connection_manager + http_filters: + - name: envoy.filters.http.jwt_authn + typed_config: + "@type": type.googleapis.com/envoy.config.filter.http.jwt_authn.v2alpha.JwtAuthentication +``` + +This JWT authentication HTTP filter contains two fields: +* The `providers` field specifies how a JWT should be verified, such as where to extract the token, where to fetch the + public key ([JWKS][]) and where to output its payload. This field is built from the source resource `namespace-name`, and + the JWT provider name of an AuthenticationFilter. +* The `rules` field specifies matching rules and their requirements. If a request matches a rule, its requirement + applies. The requirement specifies which JWT providers should be used. This field is built from a HTTPRoute + `matches` rule that references the AuthenticationFilter. When a referenced Authentication specifies multiple + `jwtProviders`, the JWT is considered valid if __any__ of the providers successfully validate the JWT. + +The following JWT authentication HTTP filter `providers` configuration is created from the above AuthenticationFilter. + +```yaml +providers: + example: + issuer: https://www.example.com + audiences: + - foo.com + remote_jwks: + http_uri: + uri: https://foo.com/jwt/public-key/jwks.json + cluster: example_jwks_cluster + timeout: 1s +``` + +The following JWT authentication HTTP filter `rules` configuration is created from the above HTTPRoute. + +```yaml +rules: + - match: + prefix: /foo + requires: + provider_name: default-example-example +``` + +### Example 2: Two HTTPRoutes with Different AuthenticationFilters + +The following example contains: +* Two HTTPRoutes with different hostnames. +* Each HTTPRoute references a different AuthenticationFilter. +* Each AuthenticationFilter contains a different JWT provider. + +```yaml +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: AuthenticationFilter +metadata: + name: example1 +spec: + type: JWT + jwtProviders: + - name: example1 + issuer: https://www.example1.com + audiences: + - foo.com + remoteJwks: + uri: https://foo.com/jwt/public-key/jwks.json +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: AuthenticationFilter +metadata: + name: example2 +spec: + type: JWT + jwtProviders: + - name: example2 + issuer: https://www.example2.com + audiences: + - bar.com + remoteJwks: + uri: https://bar.com/jwt/public-key/jwks.json +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: example1 +spec: + hostnames: + - www.example1.com + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + rules: + - matches: + - path: + type: PathPrefix + value: /foo + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: AuthenticationFilter + name: example1 + backendRefs: + - name: backend + port: 3000 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: example2 +spec: + hostnames: + - www.example2.com + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + rules: + - matches: + - path: + type: PathPrefix + value: /bar + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: AuthenticationFilter + name: example2 + backendRefs: + - name: backend2 + port: 3000 +``` + +The following xDS configuration is created from the above example resources: + +```yaml +configs: +... +dynamic_listeners: + - name: default-eg-http + ... + default_filter_chain: + filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + '@type': >- + type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: http + rds: + config_source: + ... + route_config_name: default-eg-http + http_filters: + - name: envoy.filters.http.jwt_authn + typed_config: + '@type': >- + type.googleapis.com/envoy.config.filter.http.jwt_authn.v2alpha.JwtAuthentication + providers: + default-example1-example1: + issuer: https://www.example1.com + audiences: + - foo.com + remote_jwks: + http_uri: + uri: https://foo.com/jwt/public-key/jwks.json + cluster: default-example1-example1-jwt + default-example2-example2: + issuer: https://www.example2.com + audiences: + - bar.com + remote_jwks: + http_uri: + uri: https://bar.com/jwt/public-key/jwks.json + cluster: default-example2-example2-jwt + rules: + - match: + exact: /foo + requires: + provider_name: default-example1-example1 + - match: + exact: /bar + requires: + provider_name: default-example2-example2 + - name: envoy.filters.http.router + typed_config: + '@type': >- + type.googleapis.com/envoy.extensions.filters.http.router.v3.Router +dynamic_route_configs: + - route_config: + '@type': type.googleapis.com/envoy.config.route.v3.RouteConfiguration + name: default-eg-http + virtual_hosts: + - name: default-eg-http + domains: + - '*' + routes: + - match: + prefix: /foo + headers: + - name: ':authority' + string_match: + exact: www.example1.com + route: + cluster: default-backend-rule-0-match-0-www.example1.com + - match: + prefix: /bar + headers: + - name: ':authority' + string_match: + exact: www.example2.com + route: + cluster: default-backend2-rule-0-match-0-www.example2.com +dynamic_active_clusters: + - cluster: + name: default-backend-rule-0-match-0-www.example.com + ... + endpoints: + - locality: {} + lb_endpoints: + - endpoint: + address: + socket_address: + address: $BACKEND_SERVICE1_IP + port_value: 3000 + - cluster: + '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster + name: default-backend-rule-1-match-0-www.example.com + ... + endpoints: + - locality: {} + lb_endpoints: + - endpoint: + address: + socket_address: + address: $BACKEND_SERVICE2_IP + port_value: 3000 +... +``` + +__Note:__ The JWT provider cluster and route is omitted from the above example for brevity. + +### Implementation Outline + +* Update the Kubernetes provider to get/watch AuthenticationFilter resources that are referenced by managed HTTPRoutes. + Add the referenced AuthenticationFilter object to the resource map and publish it. +* Update the resource translator to include the AuthenticationFilter API in HTTPRoute processing. +* Update the xDS translator to translate an AuthenticationFilter into xDS resources. The translator should perform the + following: + * Convert a list of JWT rules from the xds IR into an Envoy JWT filter config. + * Create a JWT authentication HTTP filter. + * Build the HTTP Connection Manager (HCM) HTTP filters. + * Build the HCM. + * When building the Listener, create an HCM for each filter-chain. + +## Adding Authentication Types + +Additional authentication types can be added in the future through the `AuthenticationFilterType` API. For +example, to add the `Foo` authentication type: + +Define the `Foo` authentication provider: + +```go +package v1alpha1 + +// FooAuthenticationFilterProvider defines the "Foo" authentication filter provider type. +type FooAuthenticationFilterProvider struct { + // TODO: Define fields of the Foo authentication filter provider type. +} +``` + +Add the `FooAuthenticationFilterProvider` type to `AuthenticationFilterSpec`: + +```go +package v1alpha1 + +type AuthenticationFilterSpec struct { + ... + + // Foo defines the Foo authentication type. For additional + // details, see: + // + // + // + // +optional + Foo *FooAuthenticationFilterProvider +} +``` + +Lastly, add the type to the `AuthenticationType` enum: + +```go +// AuthenticationType is a type of authentication provider. +// +kubebuilder:validation:Enum=JWT,FOO +type AuthenticationFilterType string + +const ( + // JwtAuthenticationProviderType is the JWT authentication provider type. + FooAuthenticationFilterProviderType AuthenticationFilterType = "FOO" +) +``` + +The AuthenticationFilter API should support additional authentication types in the future, for example: +- OAuth2 +- OIDC + +## Outstanding Questions + +- If Envoy Gateway owns the AuthenticationFilter API, is an xDS IR equivalent needed? +- Should local [JWKS][] be implemented before remote [JWKS][]? +- How should Envoy obtain the trusted CA for a remote [JWKS][]? +- Should HTTPS be the only supported scheme for remote [JWKS][]? +- Should OR'ing JWT providers be supported? +- Should Authentication provide status? +- Are the API field validation rules acceptable? + +[Issue 336]: https://github.com/envoyproxy/gateway/issues/336 +[Gateway API]: https://gateway-api.sigs.k8s.io/ +[support levels]: https://gateway-api.sigs.k8s.io/concepts/conformance/?h=extended#2-support-levels +[JWT]: https://jwt.io/ +[HTTPRoute]: https://gateway-api.sigs.k8s.io/api-types/httproute/ +[extension points]: https://gateway-api.sigs.k8s.io/concepts/api-overview/?h=extension#extension-points +[HTTPRouteFilter]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRouteFilter +[JWKS]: https://www.rfc-editor.org/rfc/rfc7517 +[JWT authentication filter]: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/jwt_authn_filter#config-http-filters-jwt-authn +[PR 773]: https://github.com/envoyproxy/gateway/pull/733 diff --git a/docs/v0.4/design/roadmap.md b/docs/v0.4/design/roadmap.md new file mode 100644 index 00000000000..735f19e6981 --- /dev/null +++ b/docs/v0.4/design/roadmap.md @@ -0,0 +1,80 @@ +# Roadmap + +This document serves as a high-level reference for Envoy Gateway users and contributors to understand the direction of +the project. + +## Contributing to the Roadmap + +- To add a feature to the roadmap, create an [issue][issue] or join a [community meeting][meeting] to discuss your use + case. If your feature is accepted, a maintainer will assign your issue to a [release milestone][milestones] and update + this document accordingly. +- To help with an existing roadmap item, comment on or assign yourself to the associated issue. +- If a roadmap item doesn't have an issue, create one, assign yourself to the issue, and reference this document. A + maintainer will submit a [pull request][PR] to add the feature to the roadmap. __Note:__ The feature should be + discussed in an issue or a community meeting before implementing it. + +If you don't know where to start contributing, help is needed to reduce technical, automation, and documentation debt. +Look for issues with the `help wanted` label to get started. + +## Details + +Roadmap features and timelines may change based on feedback, community contributions, etc. If you depend on a specific +roadmap item, you're encouraged to attend a community meeting to discuss the details, or help us deliver the feature by +contributing to the project. + +`Last Updated: November 2022` + +### [v0.2.0][v0.2.0]: Establish a Solid Foundation + +- Complete the core Envoy Gateway implementation- [Issue #60][60]. +- Establish initial testing, e2e, integration, etc- [Issue #64][64]. +- Establish user and developer project documentation- [Issue #17][17]. +- Achieve Gateway API conformance (e.g. routing, LB, Header transformation, etc.)- [Issue #65][65]. +- Setup a CI/CD pipeline- [Issue #63][63]. + +### [v0.3.0][v0.3.0]: Drive Advanced Features through Extension Mechanisms + +- Support extended Gateway API fields [Issue #707][707]. +- Support experimental Gateway APIs such as TCPRoute [Issue #643][643], UDPRoute [Issue #641][641] and GRPCRoute [Issue #642][642]. +- Establish guidelines for leveragaing Gateway API extensions [Issue #675][675]. +- Rate Limiting [Issue #670][670]. +- Authentication [Issue #336][336]. + +### [v0.4.0][v0.4.0]: Customizing Envoy Gateway + +- Extending Envoy Gateway control plane [Issue #20][20] +- Helm based installation for Envoy Gateway [Issue #650][650] +- Customizing managed Envoy Proxy Kubernetes resource fields [Issue #648][648] +- Configuring xDS Resources [Issue #24][24] and [Issue #31][31]. + + +### [v0.5.0][v0.5.0]: Observability and Scale + +- Observability for control plane and data plane [Issue #701][701]. + +[issue]: https://github.com/envoyproxy/gateway/issues +[meeting]: https://docs.google.com/document/d/1leqwsHX8N-XxNEyTflYjRur462ukFxd19Rnk3Uzy55I/edit?usp=sharing +[pr]: https://github.com/envoyproxy/gateway/compare +[milestones]: https://github.com/envoyproxy/gateway/milestones +[v0.2.0]: https://github.com/envoyproxy/gateway/milestone/1 +[v0.3.0]: https://github.com/envoyproxy/gateway/milestone/7 +[v0.4.0]: https://github.com/envoyproxy/gateway/milestone/12 +[v0.5.0]: https://github.com/envoyproxy/gateway/milestone/13 +[17]: https://github.com/envoyproxy/gateway/issues/17 +[20]: https://github.com/envoyproxy/gateway/issues/20 +[24]: https://github.com/envoyproxy/gateway/issues/24 +[31]: https://github.com/envoyproxy/gateway/issues/31 +[60]: https://github.com/envoyproxy/gateway/issues/60 +[63]: https://github.com/envoyproxy/gateway/issues/63 +[64]: https://github.com/envoyproxy/gateway/issues/64 +[65]: https://github.com/envoyproxy/gateway/issues/65 +[336]: https://github.com/envoyproxy/gateway/issues/336 +[641]: https://github.com/envoyproxy/gateway/issues/641 +[642]: https://github.com/envoyproxy/gateway/issues/642 +[648]: https://github.com/envoyproxy/gateway/issues/648 +[650]: https://github.com/envoyproxy/gateway/issues/650 +[643]: https://github.com/envoyproxy/gateway/issues/643 +[670]: https://github.com/envoyproxy/gateway/issues/670 +[675]: https://github.com/envoyproxy/gateway/issues/675 +[701]: https://github.com/envoyproxy/gateway/issues/701 +[707]: https://github.com/envoyproxy/gateway/issues/707 diff --git a/docs/v0.4/design/system-design.md b/docs/v0.4/design/system-design.md new file mode 100644 index 00000000000..86114be37fa --- /dev/null +++ b/docs/v0.4/design/system-design.md @@ -0,0 +1,171 @@ +# System Design + +## Goals + +* Define the system components needed to satisfy the requirements of Envoy Gateway. + +## Non-Goals + +* Create a detailed design and interface specification for each system component. + +## Terminology + +* Control Plane- A collection of inter-related software components for providing application gateway and routing + functionality. The control plane is implemented by Envoy Gateway and provides services for managing the data plane. + These services are detailed in the [components](#components) section. +* Data Plane- Provides intelligent application-level traffic routing and is implemented as one or more Envoy proxies. + +## Architecture + +![Architecture](../images/architecture.png) + +## Configuration + +Envoy Gateway is configured statically at startup and the managed data plane is configured dynamically through +Kubernetes resources, primarily [Gateway API][gw_api] objects. + +### Static Configuration + +Static configuration is used to configure Envoy Gateway at startup, i.e. change the GatewayClass controllerName, +configure a Provider, etc. Currently, Envoy Gateway only supports configuration through a configuration file. If the +configuration file is not provided, Envoy Gateway starts-up with default configuration parameters. + +### Dynamic Configuration + +Dynamic configuration is based on the concept of a declaring the desired state of the data plane and using +reconciliation loops to drive the actual state toward the desired state. The desired state of the data plane is +defined as Kubernetes resources that provide the following services: + +* Infrastructure Management- Manage the data plane infrastructure, i.e. deploy, upgrade, etc. This configuration is + expressed through [GatewayClass][gc] and [Gateway][gw] resources. The `EnvoyProxy` [Custom Resource][cr] can be + referenced by `gatewayclass.spec.parametersRef` to modify data plane infrastructure default parameters, + e.g. expose Envoy network endpoints using a `ClusterIP` service instead of a `LoadBalancer` service. +* Traffic Routing- Define how to handle application-level requests to backend services. For example, route all HTTP + requests for "www.example.com" to a backend service running a web server. This configuration is expressed through + [HTTPRoute][hroute] and [TLSRoute][troute] resources that match, filter, and route traffic to a [backend][be]. + Although a backend can be any valid Kubernetes Group/Kind resource, Envoy Gateway only supports a [Service][svc] + reference. + +## Components + +Envoy Gateway is made up of several components that communicate in-process; how this communication happens is described +in the [Watching Components Design][wcd]. + +### Provider + +A Provider is an infrastructure component that Envoy Gateway calls to establish its runtime configuration, resolve +services, persist data, etc. As of v0.2, Kubernetes is the only implemented provider. A file provider is on the roadmap +via [Issue #37][]. Other providers can be added in the future as Envoy Gateway use cases are better understood. A +provider is configured at start up through Envoy Gateway's [static configuration](#static-configuration). + +#### Kubernetes Provider + +* Uses Kubernetes-style controllers to reconcile Kubernetes resources that comprise the + [dynamic configuration](#dynamic-configuration). +* Manages the data plane through Kubernetes API CRUD operations. +* Uses Kubernetes for Service discovery. +* Uses etcd (via Kubernetes API) to persist data. + +#### File Provider + +* Uses a file watcher to watch files in a directory that define the data plane configuration. +* Manages the data plane by calling internal APIs, e.g. `CreateDataPlane()`. +* Uses the host's DNS for Service discovery. +* If needed, the local filesystem is used to persist data. + +### Resource Watcher + +The Resource Watcher watches resources used to establish and maintain Envoy Gateway's dynamic configuration. The +mechanics for watching resources is provider-specific, e.g. informers, caches, etc. are used for the Kubernetes +provider. The Resource Watcher uses the configured provider for input and provides resources to the Resource Translator +as output. + +### Resource Translator + +The Resource Translator translates external resources, e.g. GatewayClass, from the Resource Watcher to the Intermediate +Representation (IR). It is responsible for: + +* Translating infrastructure-specific resources/fields from the Resource Watcher to the Infra IR. +* Translating proxy configuration resources/fields from the Resource Watcher to the xDS IR. + +__Note:__ The Resource Translator is implemented as the `Translator` API type in the `gatewayapi` package. + +### Intermediate Representation (IR) + +The Intermediate Representation defines internal data models that external resources are translated into. This allows +Envoy Gateway to be decoupled from the external resources used for dynamic configuration. The IR consists of an Infra IR +used as input for the Infra Manager and an xDS IR used as input for the xDS Translator. + +* Infra IR- Used as the internal definition of the managed data plane infrastructure. +* xDS IR- Used as the internal definition of the managed data plane xDS configuration. + +### xDS Translator + +The xDS Translator translates the xDS IR into xDS Resources that are consumed by the xDS server. + +### xDS Server + +The xDS Server is a xDS gRPC Server based on [Go Control Plane][go_cp]. Go Control Plane implements the Delta xDS Server +Protocol and is responsible for using xDS to configure the data plane. + +### Infra Manager + +The Infra Manager is a provider-specific component responsible for managing the following infrastructure: + +* Data Plane - Manages all the infrastructure required to run the managed Envoy proxies. For example, CRUD Deployment, + Service, etc. resources to run Envoy in a Kubernetes cluster. +* Auxiliary Control Planes - Optional infrastructure needed to implement application Gateway features that require + external integrations with the managed Envoy proxies. For example, [Global Rate Limiting][grl] requires provisioning + and configuring the [Envoy Rate Limit Service][rls] and the [Rate Limit filter][rlf]. Such features are exposed to + users through the [Custom Route Filters][crf] extension. + +The Infra Manager consumes the Infra IR as input to manage the data plane infrastructure. + +## Design Decisions + +* Envoy Gateway consumes one [GatewayClass][gc] by comparing its configured controller name with + `spec.controllerName` of a GatewayClass. If multiple GatewayClasses exist with the same `spec.controllerName`, Envoy + Gateway follows Gateway API [guidelines][gwapi_conflicts] to resolve the conflict. + `gatewayclass.spec.parametersRef` refers to the `EnvoyProxy` custom resource for configuring the managed proxy + infrastructure. If unspecified, default configuration parameters are used for the managed proxy infrastructure. +* Envoy Gateway manages [Gateways][gw] that reference its GatewayClass. + * A Gateway resource causes Envoy Gateway to provision managed Envoy proxy infrastructure. + * Envoy Gateway groups Listeners by Port and collapses each group of Listeners into a single Listener if the Listeners + in the group are compatible. Envoy Gateway considers Listeners to be compatible if all the following conditions are + met: + * Either each Listener within the group specifies the “HTTP” Protocol or each Listener within the group specifies + either the “HTTPS” or “TLS” Protocol. + * Each Listener within the group specifies a unique "Hostname". + * As a special case, one Listener within a group may omit "Hostname", in which case this Listener matches when no + other Listener matches. + * Envoy Gateway does __not__ merge listeners across multiple Gateways. +* Envoy Gateway follows Gateway API [guidelines][gwapi_conflicts] to resolve any conflicts. + * A Gateway `listener` corresponds to an Envoy proxy [Listener][listener]. +* An [HTTPRoute][hroute] resource corresponds to an Envoy proxy [Route][route]. + * Each [backendRef][be_ref] corresponds to an Envoy proxy [Cluster][cluster]. +* The goal is to make Envoy Gateway components extensible in the future. See the [roadmap][] for additional details. + +The draft for this document is [here][draft_design]. + +[gw_api]: https://gateway-api.sigs.k8s.io +[gc]: https://gateway-api.sigs.k8s.io/concepts/api-overview/#gatewayclass +[gw]: https://gateway-api.sigs.k8s.io/concepts/api-overview/#gateway +[hroute]: https://gateway-api.sigs.k8s.io/concepts/api-overview/#httproute +[troute]: https://gateway-api.sigs.k8s.io/concepts/api-overview/#tlsroute +[go_cp]: https://github.com/envoyproxy/go-control-plane +[grl]: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/other_features/global_rate_limiting +[rls]: https://github.com/envoyproxy/ratelimit +[rlf]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ratelimit/v3/rate_limit.proto#envoy-v3-api-msg-extensions-filters-http-ratelimit-v3-ratelimit +[crf]: https://gateway-api.sigs.k8s.io/v1alpha2/api-types/httproute/#filters-optional +[gwapi_conflicts]: https://gateway-api.sigs.k8s.io/concepts/guidelines/#conflicts +[listener]: https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/listeners#config-listeners +[route]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-msg-config-route-v3-route +[be_ref]: https://gateway-api.sigs.k8s.io/v1alpha2/api-types/httproute/#backendrefs-optional +[cluster]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster +[draft_design]: https://docs.google.com/document/d/1riyTPPYuvNzIhBdrAX8dpfxTmcobWZDSYTTB5NeybuY/edit +[cr]: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/ +[be]: https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.BackendObjectReference +[svc]: https://kubernetes.io/docs/concepts/services-networking/service/ +[ wcd ]: ./watching.md +[Issue #37]: https://github.com/envoyproxy/gateway/issues/37 +[roadmap]: roadmap.md diff --git a/docs/v0.4/design/tcp-udp-design.md b/docs/v0.4/design/tcp-udp-design.md new file mode 100644 index 00000000000..276221b897b --- /dev/null +++ b/docs/v0.4/design/tcp-udp-design.md @@ -0,0 +1,47 @@ +# TCP and UDP Proxy Design + +Even though most of the use cases for Envoy Gateway are at Layer-7, Envoy Gateway can also work at Layer-4 to proxy TCP +and UDP traffic. This document will explore the options we have when operating Envoy Gateway at Layer-4 and explain the +design decision. + +Envoy can work as a non-transparent proxy or a transparent proxy for both [TCP](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/other_features/ip_transparency#arch-overview-ip-transparency-original-src-listener) + and [UDP](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto#envoy-v3-api-msg-extensions-filters-udp-udp-proxy-v3-udpproxyconfig) +, so ideally, Envoy Gateway should also be able to work in these two modes: + +## Non-transparent Proxy Mode +For TCP, Envoy terminates the downstream connection, connects the upstream with its own IP address, and proxies the +TCP traffic from the downstream to the upstream. + +For UDP, Envoy receives UDP datagrams from the downstream, and uses its own IP address as the sender IP address when +proxying the UDP datagrams to the upstream. + +In this mode, the upstream will see Envoy's IP address and port. + +## Transparent Proxy Mode +For TCP, Envoy terminates the downstream connection, connects the upstream with the downstream IP address, and proxies +the TCP traffic from the downstream to the upstream. + +For UDP, Envoy receives UDP datagrams from the downstream, and uses the downstream IP address as the sender IP address +when proxying the UDP datagrams to the upstream. + +In this mode, the upstream will see the original downstream IP address and Envoy's mac address. + +Note: Even in transparent mode, the upstream can't see the port number of the downstream because Envoy doesn't forward +the port number. + +## The Implications of Transparent Proxy Mode + +### Escalated Privilege +Envoy needs to bind to the downstream IP when connecting to the upstream, which means Envoy requires escalated +CAP_NET_ADMIN privileges. This is often considered as a bad security practice and not allowed in some sensitive deployments. + +### Routing +The upstream can see the original source IP, but the original port number won't be passed, so the return +traffic from the upstream must be routed back to Envoy because only Envoy knows how to send the return traffic back +to the right port number of the downstream, which requires routing at the upstream side to be set up. +In a Kubernetes cluster, Envoy Gateway will have to carefully cooperate with CNI plugins to get the routing right. + +## The Design Decision (For Now) + +The implementation will only support proxying in non-transparent mode i.e. the backend will see the source IP and +port of the deployed Envoy instance instead of the client. diff --git a/docs/v0.4/design/watching.md b/docs/v0.4/design/watching.md new file mode 100644 index 00000000000..b8477a30e2d --- /dev/null +++ b/docs/v0.4/design/watching.md @@ -0,0 +1,117 @@ +# Watching Components Design + +Envoy Gateway is made up of several components that communicate in-process. Some of them (namely Providers) watch +external resources, and "publish" what they see for other components to consume; others watch what another publishes and +act on it (such as the resource translator watches what the providers publish, and then publishes its own results that +are watched by another component). Some of these internally published results are consumed by multiple components. + +To facilitate this communication use the [watchable][] library. The `watchable.Map` type is very similar to the +standard library's `sync.Map` type, but supports a `.Subscribe` (and `.SubscribeSubset`) method that promotes a pub/sub +pattern. + +## Pub + +Many of the things we communicate around are naturally named, either by a bare "name" string or by a "name"/"namespace" +tuple. And because `watchable.Map` is typed, it makes sense to have one map for each type of thing (very similar to if +we were using native Go `map`s). For example, a struct that might be written to by the Kubernetes provider, and read by +the IR translator: + + ```go + type ResourceTable struct { + // gateway classes are cluster-scoped; no namespace + GatewayClasses watchable.Map[string, *gwapiv1b1.GatewayClass] + + // gateways are namespace-scoped, so use a k8s.io/apimachinery/pkg/types.NamespacedName as the map key. + Gateways watchable.Map[types.NamespacedName, *gwapiv1b1.Gateway] + + HTTPRoutes watchable.Map[types.NamespacedName, *gwapiv1b1.HTTPRoute] + } + ``` + +The Kubernetes provider updates the table by calling `table.Thing.Store(name, val)` and `table.Thing.Delete(name)`; +updating a map key with a value that is deep-equal (usually `reflect.DeepEqual`, but you can implement your own `.Equal` +method) the current value is a no-op; it won't trigger an event for subscribers. This is handy so that the publisher +doesn't have as much state to keep track of; it doesn't need to know "did I already publish this thing", it can just +`.Store` its data and `watchable` will do the right thing. + +## Sub + +Meanwhile, the translator and other interested components subscribe to it with `table.Thing.Subscribe` (or +`table.Thing.SubscribeSubset` if they only care about a few "Thing"s). So the translator goroutine might look like: + + ```go + func(ctx context.Context) error { + for snapshot := range k8sTable.HTTPRoutes.Subscribe(ctx) { + fullState := irInput{ + GatewayClasses: k8sTable.GatewayClasses.LoadAll(), + Gateways: k8sTable.Gateways.LoadAll(), + HTTPRoutes: snapshot.State, + } + translate(irInput) + } + } + ``` + +Or, to watch multiple maps in the same loop: + + ```go + func worker(ctx context.Context) error { + classCh := k8sTable.GatewayClasses.Subscribe(ctx) + gwCh := k8sTable.Gateways.Subscribe(ctx) + routeCh := k8sTable.HTTPRoutes.Subscribe(ctx) + for ctx.Err() == nil { + var arg irInput + select { + case snapshot := <-classCh: + arg.GatewayClasses = snapshot.State + case snapshot := <-gwCh: + arg.Gateways = snapshot.State + case snapshot := <-routeCh: + arg.Routes = snapshot.State + } + if arg.GateWayClasses == nil { + arg.GatewayClasses = k8sTable.GateWayClasses.LoadAll() + } + if arg.GateWays == nil { + arg.Gateways = k8sTable.GateWays.LoadAll() + } + if arg.HTTPRoutes == nil { + arg.HTTPRoutes = k8sTable.HTTPRoutes.LoadAll() + } + translate(irInput) + } + } + ``` + +From the updates it gets from `.Subscribe`, it can get a full view of the map being subscribed to via `snapshot.State`; +but it must read the other maps explicitly. Like `sync.Map`, `watchable.Map`s are thread-safe; while `.Subscribe` is a +handy way to know when to run, `.Load` and friends can be used without subscribing. + +There can be any number of subscribers. For that matter, there can be any number of publishers `.Store`ing things, but +it's probably wise to just have one publisher for each map. + +The channel returned from `.Subscribe` **is immediately readable** with a snapshot of the map as it existed when +`.Subscribe` was called; and becomes readable again whenever `.Store` or `.Delete` mutates the map. If multiple +mutations happen between reads (or if mutations happen between `.Subscribe` and the first read), they are coalesced in +to one snapshot to be read; the `snapshot.State` is the most-recent full state, and `snapshot.Updates` is a listing of +each of the mutations that cause this snapshot to be different than the last-read one. This way subscribers don't need +to worry about a backlog accumulating if they can't keep up with the rate of changes from the publisher. + +If the map contains anything before `.Subscribe` is called, that very first read won't include `snapshot.Updates` +entries for those pre-existing items; if you are working with `snapshot.Update` instead of `snapshot.State`, then you +must add special handling for your first read. We have a utility function `./internal/message.HandleSubscription` to +help with this. + +## Other Notes + +The common pattern will likely be that the entrypoint that launches the goroutines for each component instantiates the +map, and passes them to the appropriate publishers and subscribers; same as if they were communicating via a dumb +`chan`. + +A limitation of `watchable.Map` is that in order to ensure safety between goroutines, it does require that value types +be deep-copiable; either by having a `DeepCopy` method, being a `proto.Message`, or by containing no reference types and +so can be deep-copied by naive assignment. Fortunately, we're using `controller-gen` anyway, and `controller-gen` can +generate `DeepCopy` methods for us: just stick a `// +k8s:deepcopy-gen=true` on the types that you want it to generate +methods for. + +[watchable]: https://pkg.go.dev/github.com/telepresenceio/watchable diff --git a/docs/v0.4/design_docs.rst b/docs/v0.4/design_docs.rst new file mode 100644 index 00000000000..5bd3936b7b9 --- /dev/null +++ b/docs/v0.4/design_docs.rst @@ -0,0 +1,19 @@ +Design Docs +=========== + +Learn about the internal details of Envoy Gateway. + +.. toctree:: + :maxdepth: 1 + + design/system-design + design/gatewayapi-support + design/gatewayapi-translator + design/watching + design/config-api + design/tcp-udp-design + design/egctl + design/rate-limit + design/request-authentication + design/bootstrap + design/extending-envoy-gateway diff --git a/docs/v0.4/dev/CODEOWNERS.md b/docs/v0.4/dev/CODEOWNERS.md new file mode 100644 index 00000000000..d4229b6b23f --- /dev/null +++ b/docs/v0.4/dev/CODEOWNERS.md @@ -0,0 +1,15 @@ +# Maintainers + +## The following maintainers, listed in alphabetical order, own everything + +- @AliceProxy +- @arkodg +- @skriss +- @Xunzhuo +- @youngnick +- @zirain + +## Emeritus Maintainers + +- @danehans +- @alexgervais diff --git a/docs/v0.4/dev/CODE_OF_CONDUCT.md b/docs/v0.4/dev/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..a0a295770f3 --- /dev/null +++ b/docs/v0.4/dev/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Community Code of Conduct + +Gateway follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). diff --git a/docs/v0.4/dev/CONTRIBUTING.md b/docs/v0.4/dev/CONTRIBUTING.md new file mode 100644 index 00000000000..04d95bb675d --- /dev/null +++ b/docs/v0.4/dev/CONTRIBUTING.md @@ -0,0 +1,186 @@ +# Contributing + +We welcome contributions from the community. Please carefully review the [project goals](GOALS.md) +and following guidelines to streamline your contributions. + +## Communication + +* Before starting work on a major feature, please contact us via GitHub or Slack. We will ensure no + one else is working on it and ask you to open a GitHub issue. +* A "major feature" is defined as any change that is > 100 LOC altered (not including tests), or + changes any user-facing behavior. We will use the GitHub issue to discuss the feature and come to + agreement. This is to prevent your time being wasted, as well as ours. The GitHub review process + for major features is also important so that [affiliations with commit access](CODEOWNERS.md) can + come to agreement on the design. If it's appropriate to write a design document, the document must + be hosted either in the GitHub issue, or linked to from the issue and hosted in a world-readable + location. +* Small patches and bug fixes don't need prior communication. + +## Inclusivity + +The Envoy Gateway community has an explicit goal to be inclusive to all. As such, all PRs must adhere +to the following guidelines for all code, APIs, and documentation: + +* The following words and phrases are not allowed: + * *Whitelist*: use allowlist instead. + * *Blacklist*: use denylist or blocklist instead. + * *Master*: use primary instead. + * *Slave*: use secondary or replica instead. +* Documentation should be written in an inclusive style. The [Google developer + documentation](https://developers.google.com/style/inclusive-documentation) contains an excellent + reference on this topic. +* The above policy is not considered definitive and may be amended in the future as industry best + practices evolve. Additional comments on this topic may be provided by maintainers during code + review. + +## Submitting a PR + +* Fork the repo. +* Hack +* DCO sign-off each commit. This can be done with `git commit -s`. +* Submit your PR. +* Tests will automatically run for you. +* We will **not** merge any PR that is not passing tests. +* PRs are expected to have 100% test coverage for added code. This can be verified with a coverage + build. If your PR cannot have 100% coverage for some reason please clearly explain why when you + open it. +* Any PR that changes user-facing behavior **must** have associated documentation in the [docs](https://github.com/envoyproxy/gateway/tree/main/docs) folder of the repo as + well as the [changelog](../releases). +* All code comments and documentation are expected to have proper English grammar and punctuation. + If you are not a fluent English speaker (or a bad writer ;-)) please let us know and we will try + to find some help but there are no guarantees. +* Your PR title should be descriptive, and generally start with type that contains a subsystem name with `()` if necessary + and summary followed by a colon. format `chore/docs/feat/fix/refactor/style/test: summary`. + Examples: + * "docs: fix grammar error" + * "feat(translator): add new feature" + * "fix: fix xx bug" + * "chore: change ci & build tools etc" +* Your PR commit message will be used as the commit message when your PR is merged. You should + update this field if your PR diverges during review. +* Your PR description should have details on what the PR does. If it fixes an existing issue it + should end with "Fixes #XXX". +* If your PR is co-authored or based on an earlier PR from another contributor, + please attribute them with `Co-authored-by: name `. See + GitHub's [multiple author + guidance](https://help.github.com/en/github/committing-changes-to-your-project/creating-a-commit-with-multiple-authors) + for further details. +* When all tests are passing and all other conditions described herein are satisfied, a maintainer + will be assigned to review and merge the PR. +* Once you submit a PR, *please do not rebase it*. It's much easier to review if subsequent commits + are new commits and/or merges. We squash and merge so the number of commits you have in the PR + doesn't matter. +* We expect that once a PR is opened, it will be actively worked on until it is merged or closed. + We reserve the right to close PRs that are not making progress. This is generally defined as no + changes for 7 days. Obviously PRs that are closed due to lack of activity can be reopened later. + Closing stale PRs helps us to keep on top of all the work currently in flight. + +## Maintainer PR Review Policy + +* See [CODEOWNERS.md](CODEOWNERS.md) for the current list of maintainers. +* A maintainer representing a different affiliation from the PR owner is required to review and + approve the PR. +* When the project matures, it is expected that a "domain expert" for the code the PR touches should + review the PR. This person does not require commit access, just domain knowledge. +* The above rules may be waived for PRs which only update docs or comments, or trivial changes to + tests and tools (where trivial is decided by the maintainer in question). +* If there is a question on who should review a PR please discuss in Slack. +* Anyone is welcome to review any PR that they want, whether they are a maintainer or not. +* Please make sure that the PR title, commit message, and description are updated if the PR changes + significantly during review. +* Please **clean up the title and body** before merging. By default, GitHub fills the squash merge + title with the original title, and the commit body with every individual commit from the PR. + The maintainer doing the merge should make sure the title follows the guidelines above and should + overwrite the body with the original commit message from the PR (cleaning it up if necessary) + while preserving the PR author's final DCO sign-off. + +## Decision making + +This is a new and complex project, and we need to make a lot of decisions very quickly. +To this end, we've settled on this process for making (possibly contentious) decisions: + +* For decisions that need a record, we create an issue. +* In that issue, we discuss opinions, then a maintainer can call for a vote in a comment. +* Maintainers can cast binding votes on that comment by reacting or replying in another comment. +* Non-maintainer community members are welcome to cast non-binding votes by either of these methods. +* Voting will be resolved by simple majority. +* In the event of deadlocks, the question will be put to steering instead. + +## DCO: Sign your work + +The sign-off is a simple line at the end of the explanation for the +patch, which certifies that you wrote it or otherwise have the right to +pass it on as an open-source patch. The rules are pretty simple: if you +can certify the below (from +[developercertificate.org](https://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +using your real name (sorry, no pseudonyms or anonymous contributions.) + +You can add the sign-off when creating the git commit via `git commit -s`. + +If you want this to be automatic you can set up some aliases: + +```bash +git config --add alias.amend "commit -s --amend" +git config --add alias.c "commit -s" +``` + +## Fixing DCO + +If your PR fails the DCO check, it's necessary to fix the entire commit history in the PR. Best +practice is to [squash](https://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) +the commit history to a single commit, append the DCO sign-off as described above, and [force +push](https://git-scm.com/docs/git-push#git-push---force). For example, if you have 2 commits in +your history: + +```bash +git rebase -i HEAD^^ +(interactive squash + DCO append) +git push origin -f +``` + +Note, that in general rewriting history in this way is a hindrance to the review process and this +should only be done to correct a DCO mistake. diff --git a/docs/v0.4/dev/DOCS.md b/docs/v0.4/dev/DOCS.md new file mode 100644 index 00000000000..fb49b9d55dd --- /dev/null +++ b/docs/v0.4/dev/DOCS.md @@ -0,0 +1,63 @@ +# Working on the Envoy Gateway Docs + +The documentation for the Envoy Gateway lives in the `docs/` directory. Any +individual document can be written using either [reStructuredText] or [Markdown], +you can choose the format that you're most comfortable with when working on the +documentation. + +## Documentation Structure + +We supported the versioned Docs now, the directory name under docs represents +the version of docs. The root of the latest site is in `docs/latest/index.rst`. +This is probably where to start if you're trying to understand how things fit together. + +Note that the new contents should be added to `docs/latest` and will be cut off at +the next release. The contents under `docs/v0.2.0` are auto-generated, +and usually do not need to make changes to them, unless if you find the current release pages have +some incorrect contents. If so, you should send a PR to update contents both of `docs/latest` +and `docs/v0.2.0`. + +It's important to note that a given document _must_ have a reference in some +`.. toctree::` section for the document to be reachable. Not everything needs +to be in `docs/index.rst`'s `toctree` though. + +You can access the website which represents the current release in default, +and you can access the website which contains the latest version changes in +[Here][latest-website] or at the footer of the pages. + +## Documentation Workflow + +To work with the docs, just edit reStructuredText or Markdown files in `docs`, +then run + +```bash +make docs +``` + +This will create `docs/html` with the built HTML pages. You can view the docs +either simply by pointing a web browser at the `file://` path to your +`docs/html`, or by firing up a static webserver from that directory, e.g. + +``` shell +make docs-serve +``` + +If you want to generate a new release version of the docs, like `v0.3.0`, then run + +```bash +make docs-release TAG=v0.3.0 +``` + +This will update the VERSION file at the project root, which records current release version, +and it will be used in the pages version context and binary version output. Also, this will generate +new dir `docs/v0.3.0`, which contains docs at v0.3.0 and updates artifact links to `v0.3.0` +in all files under `docs/v0.3.0/user`, like `quickstart.md`, `http-routing.md` and etc. + +## Publishing Docs + +Whenever docs are pushed to `main`, CI will publish the built docs to GitHub +Pages. For more details, see `.github/workflows/docs.yaml`. + +[reStructuredText]: https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html +[Markdown]: https://daringfireball.net/projects/markdown/syntax +[latest-website]: https://gateway.envoyproxy.io/latest diff --git a/docs/v0.4/dev/GOALS.md b/docs/v0.4/dev/GOALS.md new file mode 120000 index 00000000000..f2174592394 --- /dev/null +++ b/docs/v0.4/dev/GOALS.md @@ -0,0 +1 @@ +../../../GOALS.md \ No newline at end of file diff --git a/docs/v0.4/dev/README.md b/docs/v0.4/dev/README.md new file mode 100644 index 00000000000..2d4f13e9e80 --- /dev/null +++ b/docs/v0.4/dev/README.md @@ -0,0 +1,156 @@ +# Developer Guide + +Envoy Gateway is built using a [make][]-based build system. Our CI is based on [Github Actions][] using [workflows][]. + +## Prerequisites + +### go + +* Version: 1.20 +* Installation Guide: https://go.dev/doc/install + +### make + +* Recommended Version: 4.0 or later +* Installation Guide: https://www.gnu.org/software/make + +### docker + +* Optional when you want to build a Docker image or run `make` inside Docker. +* Recommended Version: 20.10.16 +* Installation Guide: https://docs.docker.com/engine/install + +### python3 + +* Need a `python3` program +* Must have a functioning `venv` module; this is part of the standard + library, but some distributions (such as Debian and Ubuntu) replace + it with a stub and require you to install a `python3-venv` package + separately. + +## Quickstart + +* Run `make help` to see all the available targets to build, test and run Envoy Gateway. + +### Building + +* Run `make build` to build all the binaries. +* Run `make build BINS="envoy-gateway"` to build the Envoy Gateway binary. +* Run `make build BINS="egctl"` to build the egctl binary. + +__Note:__ The binaries get generated in the `bin/$OS/$ARCH` directory, for example, `bin/linux/amd64/`. + +### Testing + +* Run `make test` to run the golang tests. + +### Running Linters + +* Run `make lint` to make sure your code passes all the linter checks. + +### Building and Pushing the Image + +* Run `IMAGE=docker.io/you/gateway-dev make image` to build the docker image. +* Run `IMAGE=docker.io/you/gateway-dev make push-multiarch` to build and push the multi-arch docker image. + +__Note:__ Replace `IMAGE` with your registry's image name. + +### Deploying Envoy Gateway for Test/Dev + +* Run `make create-cluster` to create a [Kind][] cluster. + +#### Option 1: Use the Latest [gateway-dev][] Image + +* Run `TAG=latest make kube-deploy` to deploy Envoy Gateway in the Kind cluster using the latest image. Replace `latest` + to use a different image tag. + +#### Option 2: Use a Custom Image + +* Run `make kube-install-image` to build an image from the tip of your current branch and load it in the Kind cluster. +* Run `make kube-deploy` to install Envoy Gateway into the Kind cluster using your custom image. + +### Deploying Envoy Gateway in Kubernetes + +* Run `TAG=latest make kube-deploy` to deploy Envoy Gateway using the latest image into a Kubernetes cluster (linked to + the current kube context). Preface the command with `IMAGE` or replace `TAG` to use a different Envoy Gateway image or + tag. +* Run `make kube-undeploy` to uninstall Envoy Gateway from the cluster. + +__Note:__ Envoy Gateway is tested against Kubernetes v1.24.0. + +### Demo Setup + +* Run `make kube-demo` to deploy a demo backend service, gatewayclass, gateway and httproute resource +(similar to steps outlined in the [Quickstart][] docs) and test the configuration. +* Run `make kube-demo-undeploy` to delete the resources created by the `make kube-demo` command. + +### Run Gateway API Conformance Tests + +The commands below deploy Envoy Gateway to a Kubernetes cluster and run the Gateway API conformance tests. Refer to the +Gateway API [conformance homepage][] to learn more about the tests. If Envoy Gateway is already installed, run +`TAG=latest make run-conformance` to run the conformance tests. + +#### On a Linux Host + +* Run `TAG=latest make conformance` to create a Kind cluster, install Envoy Gateway using the latest [gateway-dev][] + image, and run Gateway API conformance tests. + +#### On a Mac Host + +Since Mac doesn't support [directly exposing][] the Docker network to the Mac host, use one of the following +workarounds to run conformance tests: + +* Deploy your own Kubernetes cluster or use Docker Desktop with [Kubernetes support][] and then run + `TAG=latest make kube-deploy run-conformance`. This will install Envoy Gateway using the latest [gateway-dev][] image + to the Kubernetes cluster using the current kubectl context and run the conformance tests. Use `make kube-undeploy` to + uninstall Envoy Gateway. +* Install and run [Docker Mac Net Connect][mac_connect] and then run `TAG=latest make conformance`. + +__Note:__ Preface commands with `IMAGE` or replace `TAG` to use a different Envoy Gateway image or tag. If `TAG` +is unspecified, the short SHA of your current branch is used. + +### Debugging the Envoy Config + +An easy way to view the envoy config that Envoy Gateway is using is to port-forward to the admin interface port +(currently `19000`) on the Envoy deployment that corresponds to a Gateway so that it can be accessed locally. + +Get the name of the Envoy deployment. The following example is for Gateway `eg` in the `default` namespace: + +```shell +export ENVOY_DEPLOYMENT=$(kubectl get deploy -n envoy-gateway-system --selector=gateway.envoyproxy.io/owning-gateway-namespace=default,gateway.envoyproxy.io/owning-gateway-name=eg -o jsonpath='{.items[0].metadata.name}') +``` + +Port forward the admin interface port: + +```shell +kubectl port-forward deploy/${ENVOY_DEPLOYMENT} -n envoy-gateway-system 19000:19000 +``` + +Now you are able to view the running Envoy configuration by navigating to `127.0.0.1:19000/config_dump`. + +There are many other endpoints on the [Envoy admin interface][] that may be helpful when debugging. + +### JWT Testing + +An example [JSON Web Token (JWT)][jwt] and [JSON Web Key Set (JWKS)][jwks] are used for the [request authentication][] +user guide. The JWT was created by the [JWT Debugger][], using the `RS256` algorithm. The public key from the JWTs +verify signature was copied to [JWK Creator][] for generating the JWK. The JWK Creator was configured with matching +settings, i.e. `Signing` public key use and the `RS256` algorithm. The generated JWK was wrapped in a JWKS structure +and is hosted in the repo. + +[Quickstart]: https://github.com/envoyproxy/gateway/blob/main/docs/user/quickstart.md +[make]: https://www.gnu.org/software/make/ +[Github Actions]: https://docs.github.com/en/actions +[workflows]: https://github.com/envoyproxy/gateway/tree/main/.github/workflows +[Kind]: https://kind.sigs.k8s.io/ +[conformance homepage]: https://gateway-api.sigs.k8s.io/concepts/conformance/ +[directly exposing]: https://kind.sigs.k8s.io/docs/user/loadbalancer/ +[Kubernetes support]: https://docs.docker.com/desktop/kubernetes/ +[gateway-dev]: https://hub.docker.com/r/envoyproxy/gateway-dev/tags +[mac_connect]: https://github.com/chipmk/docker-mac-net-connect +[Envoy admin interface]: https://www.envoyproxy.io/docs/envoy/latest/operations/admin#operations-admin-interface +[jwt]: https://tools.ietf.org/html/rfc7519 +[jwks]: https://tools.ietf.org/html/rfc7517 +[request authentication]: https://gateway.envoyproxy.io/latest/user/authn.html +[JWT Debugger]: https://jwt.io/ +[JWK Creator]: https://russelldavies.github.io/jwk-creator/ diff --git a/docs/v0.4/dev/releasing.md b/docs/v0.4/dev/releasing.md new file mode 100644 index 00000000000..f0004caf336 --- /dev/null +++ b/docs/v0.4/dev/releasing.md @@ -0,0 +1,195 @@ +# Release Process + +This document guides maintainers through the process of creating an Envoy Gateway release. + +- [Release Candidate](#release-candidate) +- [Minor Release](#minor-release) +- [Announce the Release](#announce-the-release) + +## Release Candidate + +The following steps should be used for creating a release candidate. + +### Prerequisites + +- Permissions to push to the Envoy Gateway repository. + +Set environment variables for use in subsequent steps: + +```shell +export MAJOR_VERSION=0 +export MINOR_VERSION=3 +export RELEASE_CANDIDATE_NUMBER=1 +export GITHUB_REMOTE=origin +``` + +1. Clone the repo, checkout the `main` branch, ensure it’s up-to-date, and your local branch is clean. +2. Create a topic branch for adding the release notes and updating the [VERSION][] file with the release version. Refer to previous [release notes][] and [VERSION][] for additional details. +3. Sign, commit, and push your changes to your fork. +4. Submit a [Pull Request][] to merge the changes into the `main` branch. Do not proceed until your PR has merged and + the [Build and Test][] has successfully completed. +5. Create a new release branch from `main`. The release branch should be named + `release/v${MAJOR_VERSION}.${MINOR_VERSION}`, e.g. `release/v0.3`. + + ```shell + git checkout -b release/v${MAJOR_VERSION}.${MINOR_VERSION} + ``` + +6. Push the branch to the Envoy Gateway repo. + + ```shell + git push ${GITHUB_REMOTE} release/v${MAJOR_VERSION}.${MINOR_VERSION} + ``` + +7. Create a topic branch for updating the Envoy proxy image to the tag supported by the release. Reference [PR #958][] + for additional details on updating the image tag. +8. Sign, commit, and push your changes to your fork. +9. Submit a [Pull Request][] to merge the changes into the `release/v${MAJOR_VERSION}.${MINOR_VERSION}` branch. Do not + proceed until your PR has merged into the release branch and the [Build and Test][] has completed for your PR. +10. Ensure your release branch is up-to-date and tag the head of your release branch with the release candidate number. + + ```shell + git tag -a v${MAJOR_VERSION}.${MINOR_VERSION}.0-rc.${RELEASE_CANDIDATE_NUMBER} -m 'Envoy Gateway v${MAJOR_VERSION}.${MINOR_VERSION}.0-rc.${RELEASE_CANDIDATE_NUMBER} Release Candidate' + ``` + +11. Push the tag to the Envoy Gateway repository. + + ```shell + git push ${GITHUB_REMOTE} v${MAJOR_VERSION}.${MINOR_VERSION}.0-rc.${RELEASE_CANDIDATE_NUMBER} + ``` + +12. This will trigger the [release GitHub action][] that generates the release, release artifacts, etc. +13. Confirm that the [release workflow][] completed successfully. +14. Confirm that the Envoy Gateway [image][] with the correct release tag was published to Docker Hub. +15. Confirm that the [release][] was created. +16. Note that the [Quickstart Guide][] references are __not__ updated for release candidates. However, test + the quickstart steps using the release candidate by manually updating the links. +17. [Generate][] the GitHub changelog. +18. Ensure you check the "This is a pre-release" checkbox when editing the GitHub release. +19. If you find any bugs in this process, please create an issue. + +## Minor Release + +The following steps should be used for creating a minor release. + +### Prerequisites + +- Permissions to push to the Envoy Gateway repository. +- A release branch that has been cut from the corresponding release candidate. Refer to the + [Release Candidate](#release-candidate) section for additional details on cutting a release candidate. + +Set environment variables for use in subsequent steps: + +```shell +export MAJOR_VERSION=0 +export MINOR_VERSION=3 +export GITHUB_REMOTE=origin +``` + +1. Clone the repo, checkout the `main` branch, ensure it’s up-to-date, and your local branch is clean. +2. Create a topic branch for adding the release notes, release announcement, and versioned release docs. + + 1. Create the release notes. Reference previous [release notes][] for additional details. __Note:__ The release + notes should be an accumulation of the release candidate release notes and any changes since the release + candidate. + 2. Create a release announcement. Refer to [PR #635] as an example release announcement. + 3. Generate the versioned release docs: + + ``` shell + make docs-release TAG=v${MAJOR_VERSION}.${MINOR_VERSION} + ``` + +3. Sign, commit, and push your changes to your fork. +4. Submit a [Pull Request][] to merge the changes into the `main` branch. Do not proceed until all your PRs have merged + and the [Build and Test][] has completed for your final PR. + +5. Checkout the release branch. + + ```shell + git checkout -b release/v${MAJOR_VERSION}.${MINOR_VERSION} $GITHUB_REMOTE/release/v${MAJOR_VERSION}.${MINOR_VERSION} + ``` + +6. If the tip of the release branch does not match the tip of `main`, perform the following: + + 1. Create a topic branch from the release branch. + 2. Cherry-pick the commits from `main` that differ from the release branch. + 3. Run tests locally, e.g. `make lint`. + 4. Sign, commit, and push your topic branch to your Envoy Gateway fork. + 5. Submit a PR to merge the topic from of your fork into the Envoy Gateway release branch. + 6. Do not proceed until the PR has merged and CI passes for the merged PR. + 7. If you are still on your topic branch, change to the release branch: + + ```shell + git checkout release/v${MAJOR_VERSION}.${MINOR_VERSION} + ``` + + 8. Ensure your local release branch is up-to-date: + + ```shell + git pull $GITHUB_REMOTE release/v${MAJOR_VERSION}.${MINOR_VERSION} + ``` + +7. Tag the head of your release branch with the release tag. For example: + + ```shell + git tag -a v${MAJOR_VERSION}.${MINOR_VERSION}.0 -m 'Envoy Gateway v${MAJOR_VERSION}.${MINOR_VERSION}.0 Release' + ``` + + __Note:__ The tag version differs from the release branch by including the `.0` patch version. + +8. Push the tag to the Envoy Gateway repository. + + ```shell + git push origin v${MAJOR_VERSION}.${MINOR_VERSION}.0 + ``` + +9. This will trigger the [release GitHub action][] that generates the release, release artifacts, etc. +10. Confirm that the [release workflow][] completed successfully. +11. Confirm that the Envoy Gateway [image][] with the correct release tag was published to Docker Hub. +12. Confirm that the [release][] was created. +13. Confirm that the steps in the [Quickstart Guide][] work as expected. +14. [Generate][] the GitHub changelog and include the following text at the beginning of the release page: + + ```console + # Release Announcement + + Check out the [v${MAJOR_VERSION}.${MINOR_VERSION} release announcement] + (https://gateway.envoyproxy.io/releases/v${MAJOR_VERSION}.${MINOR_VERSION}.html) to learn more about the release. + ``` + +If you find any bugs in this process, please create an issue. + +## Announce the Release + +It's important that the world knows about the release. Use the following steps to announce the release. + +1. Set the release information in the Envoy Gateway Slack channel. For example: + + ```shell + Envoy Gateway v${MAJOR_VERSION}.${MINOR_VERSION} has been released: https://github.com/envoyproxy/gateway/releases/tag/v${MAJOR_VERSION}.${MINOR_VERSION}.0 + ``` + +2. Send a message to the Envoy Gateway Slack channel. For example: + + ```shell + On behalf of the entire Envoy Gateway community, I am pleased to announce the release of Envoy Gateway + v${MAJOR_VERSION}.${MINOR_VERSION}. A big thank you to all the contributors that made this release possible. + Refer to the official v${MAJOR_VERSION}.${MINOR_VERSION} announcement for release details and the project docs + to start using Envoy Gateway. + ... + ``` + + Link to the GitHub release and release announcement page that highlights the release. + +[release notes]: https://github.com/envoyproxy/gateway/tree/main/release-notes +[Pull Request]: https://github.com/envoyproxy/gateway/pulls +[Quickstart Guide]: https://github.com/envoyproxy/gateway/blob/main/docs/user/quickstart.md +[Build and Test]: https://github.com/envoyproxy/gateway/blob/main/.github/workflows/build_and_test.yaml +[release GitHub action]: https://github.com/envoyproxy/gateway/blob/main/.github/workflows/release.yaml +[release workflow]: https://github.com/envoyproxy/gateway/actions/workflows/release.yaml +[image]: https://hub.docker.com/r/envoyproxy/gateway/tags +[release]: https://github.com/envoyproxy/gateway/releases +[Generate]: https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes +[PR #635]: https://github.com/envoyproxy/gateway/pull/635 +[PR #958]: https://github.com/envoyproxy/gateway/pull/958 +[VERSION]: https://github.com/envoyproxy/gateway/blob/main/VERSION diff --git a/docs/v0.4/dev_docs.rst b/docs/v0.4/dev_docs.rst new file mode 100644 index 00000000000..e546e14c1ad --- /dev/null +++ b/docs/v0.4/dev_docs.rst @@ -0,0 +1,15 @@ +Developer Docs +============== + +Learn how to contribute to Envoy Gateway. + +.. toctree:: + :maxdepth: 1 + + dev/GOALS + dev/CODE_OF_CONDUCT + dev/CODEOWNERS + dev/CONTRIBUTING + dev/README + dev/DOCS + dev/releasing diff --git a/docs/v0.4/get_involved.rst b/docs/v0.4/get_involved.rst new file mode 100644 index 00000000000..f17febd5651 --- /dev/null +++ b/docs/v0.4/get_involved.rst @@ -0,0 +1,9 @@ +Getting Involved +================ + +We welcome contributions from the community. Please carefully review the +`project goals `_ +and the +`code of conduct `_ +before diving in. + diff --git a/docs/v0.4/images/architecture.png b/docs/v0.4/images/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..1d4131fbea77efd32a37cd054d7b8639aee52016 GIT binary patch literal 449265 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%0A7c!pm~+>fP1V|T$NZSF%UWfR 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%0AI++-%B0AL-BhCQXKe`hp`gm?!TLSnYdj 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∓!|G^7!zr2F0ut+nFo zQZ4*{7$Fvd_uN2Wr68KYhFe+-SQas9{0bpOrFH{fmpN)w=sm?^9_0pFQ z(?k@1`R=6z^ZX9W5d^3ca4si4@c&*?6sL7j$)|bKA)2g}Dn!3k2AC5+=PM8sj+_FM zle1t=mG3Ld1~KG1B+M64xCF`>3T+KwPqSX#9{miG*F@(u#2o4@s#AAS!-(MFaE7G& zkVV4e{5>JAa6`i)W+)4Hm0D)Lc*YES^;L}ep0x`!Bupcs2MmBS`4tF=`35*wfJ3rP z*E@>`CB!IZyMYa`XU-TjPS}=@*9x-=*e_Vc_)Q_C2{fmt!CN}J{lq6=#l=I_?yc@R zLr%)zwpY?2Co5_E)6U_?8hM<**Q_(2@&B)|tKX`09gqcmJ!^^T)%aPU2qwT)Q~O&+ zC&4r34kUmuS3tsVKm)jY0q2WSHM2S;D0oFLu`q;Se7VomhJ^Vz+9JZ#y<>YEjVt`1 zphx%4qHqfp+6`n_Y(!hjR9dDM?ZUF^`O>WQ0l+OHW@TL!#A$RZRWq94d_{)0sJ8+B zzz_sheE>gS4uU#C6CM$bci>cM?EKvu<_X|VvEz+lwR`XLOA}oh$W`6>O1w$uc`I$O zbECyk^*!_2T4SR+b}RNItDD3QbQ2U_&Mh`X65Z6RLq1Yfx~hPR>amV01>zy_j?bkM!T6x4H>! zUhi)oJpbCD+Y#69LuQ>g4~9KZH(CZrxryNJ(ggUY*}h2tf6#40Pi8L+fqM+1cBp;V0NgLbbaL05e*Jc@GAI=z3#m&L}V99l4Z z;E~*X9TOxd4vTlkVu#8C9)`|JlrU3SwSh-17I8D)^ETZ$m;pSSx+k2CufQtI1~M** zIRwVVk0DnKW1$N~-m^aM-AuOMkCcHla05!!GSN@I{iQ;sjt?!W%$@t+@l(1DeAypr za!kdh050L%t4;Ntp3&0u4!9M!aqm0{sy7;|`==B)SlparVW$u?5hzJDd>j83p6gjC z#lj}QH3>tgD87zippCy+Vh9Zsu)S|Ro$S8wv&@*kuy&G2R*RVTCku=9{$5YU2qDy9 z*dLK03LsRtnJ@9>9}b=~9Xyn_6ya})zaI)JGyaVG2I$cvkoE+AHLac%&|z}2Mpl{T zci>2~^zF{WNe_xR=#qq~0jZu~{#$n##sO(*tY^j68qW9|;6|t<0RWOSrW|(cvo_4x z&q5f^PyHpuG+T$3kr8f*2trurNJR37!XYFo1vl8AJ&an%f* zQtLQy)_uUWGyfvj%?=ps08v&XU(R33+;`b%mqj@R5+9=*0UdTUKnXp5B%NP}0|J~K zKGp0RVIb^!9ead(gaG+>B`vRm=&~pv zZ3WZnYvnKi>Fz2Lr3wj!Vy@8gZ1$g40)2upf}Kuycga;$4;cR1iN^)Pfx!6>5;(vP z&n)$j@s!|SZ8+W%zJcu3wI}M;oCsxV#VKRUs^J_sdOm`=!va*lWz7^!diV?I>Ni9k z)(j*eFA-_!Ms1zn>L5k83*i=21J^F}XIulgC}(kEa+ML9DCC6n)>x1FSUnw7_64vu=-XLl`~h z0ocrMY(Mr@x(tO4Hz#QUq&<-vtga99SI_AePL^jBn#Y(}x4It~sAYWZmaO9_=j6Ih zhSUGD#RD4M&|xRG8_mQQXA9Qz5$2jcS(q#awx>ZxKDF!cOpZ|AfK6O+yW)* z&U@V zkhZ?2dG@(H3B3mJeKV5ZHol#;OfMYR0s>?H6bv8P>d3pdv$5@FB4hv(rU`lmL&9x5 z`A)X`kIm{dtmt+u!vt0!tY=$M?20XR928Nx^1c$HCDv-pHBU~WY;@iw4I)QNoUP|5 z@JBdSs=+F?5ge8+Kay2O*VWScE*O_}b-V8}MK+>9gY4Wj|KXc)YasgyXf*fsARBA^ z2^yXs;FYq_>(=gVxUANyv?SH~JQ@2MuAH-Ys>u1|a?b^xpN-bJ&;lL|!eQ$;ucW8^ z^{@iSQXwwJ7@Ujxaa9$Lm6gkTnLmwKU<9h0aQ6b!{uO4pQ<^JE@4v**b9jQ8J9~#~zD3gC5Z-aChb|UaU5f(SlI>42*%Qp*~8&xWbI6 zdk|(?&2aw4dp!GB!GZzZ(HJ%bSc?{fJQ{Bgk6gGj8JZYZ7Pxk+2(C@g1T&C{=0l&H zV|%WPo>68|r3i{wd;qLVakwH&B?)Rsq%EcKJ|PP}K8fJ5y@xLQ;K|$OnaboUmCyFI z`oow>E{YID4Axe<`Ey$0A1E@QW|#`4ZuI75=!?>@&AJe~X{TSz5~<_^kIvLAM^B#A zI=K-`c?7Df&f+g$-$7N2ljC}GJsGmwy)0uEpy0F=FJE39IqnTqHyh}zIKHOgMs5$4 zZcWU##8qP70->kN+O$s`Nm0CJCz)_n=$6?vkyuCndUlC8K!V-d-|bcDk|1mq2}jv4 z!;ZK=G6yifN%G`7PBDs=K2YMln6M&c_`Ff^Y?3V^v7I_fX6@9VBvla&{YrJ`DkTfeGT6gE#sB&0W$nU<%pr^b8-)8bc$;Y=I65a)JEI3rjHlo>h@knq*q&5jK z^YD3~F0r3~e3Y(}0lUNU%!@P$FrXNJ|L9hx0IT893;vR`{AcDpK{G-dU99H>36{koH>9Mu{={*AOZBe!#6Ico1}pl4Xcee z=>)IuY()m8xm(-(glnCs*Fq?d&A29RmOj1KF29i;pt`v>9NXowwdw2b=npP>YkXvj z%fOSO9#K@d{J3?A6?tc;ro7KoqL2B znYa3AkKSuc+Wt61MFCb)iu`-7zz)>Zn4~*g`_Hk@iigl`mueIvXhKQ2OC+7&p~F`j zd)mt?;d2Oo-JqJd-1!2CjS3k)=bo7*Q!8%YXpG`C^oKmTTp~_MK0~z_eXwPoyz<#4 zaDP}%EP#`{Cu;EgCBRX-$aX#1DINk10*vLN6yVmDHwXl}Ik?wZ$E$ibfz1w18M>^Oa|X49Ab+sS`%d{JGDQf9QCLI`QQ#=|g5evj{lr|?5ufcxfaO(HB~3d~_i$?0 z_6^;`UUQbqDoJTF4Y8%r+^t-u^NFv5J6_(3gV7DMI^H9L@_Y~FAq@~#`v}x+$hXRAB4`GT)NuGd#?XGZh;y{{UP#FVB=5&c&ChOxD-8C=oIQtR2 zQGaA2LH9nwA=@^;p!Vd*&R0mk4sHaO6o=h8 zi{GNs!DL5gRtH7-4XogRk~Id8aU`u$Eag|{wxjOvKdS6{lSq2*(3Urm({J&R7BL#2 zz`QHE;NUn6Rn3HagbbnwHTZ60xYx_MbBsrff(EmXVujapoS^|oeY{CRMy?e)&YZ90 z$5#~-I5_-lJ)CL}R{(*?{dhL5lORTnV)_Y;lrYa*M3gSS?fVpWu+zA=Wxh`V#uu48 z6<#=Jyj(w?q5D4HC;ak&6maf+nZg{)o#t=hW)>sZDHz@vM}?rYR(US6LF4~@`X89X z;5PSiZ>6Esy30W#l?X6H*RpmgiioMQ27=!80q3RpvsIsM)LLs|V}O5F@g4T^2*eE&7gy&%ElB(oYwZI4QV30!6YRIWhZi=(;XcF%GI9Iwyz~ zIDIfsw#2*!BN;)Q7de%zmhf~#$dp7rB zqVpo=D1?ESYx`tRAWeZWC+Ww4!oWt=xO*fT5yOEwS~WZ(QyG$uHzn-51c>L9O}Znk z=(b_?Sx@e`j#aAw2Rk8>*vfs-dymO-=!Rd(e%AkT$-1N`Z1^eIh44z7Bz2Bb2#pME z8bN);>p1ag(Uxt2KkK&A`#lnNahbVQR&;mxD3vfqzPyBq;w$~vfMAUt&-MSi$AfX3 z8y1t>PTOfveisV+T+XSv5+0v3QT(wf@?x(muE9M=xd-hi=-9{nBG%{Cw+#;PAHO+l zOwTHEa$uR_A)=t(7w>!S)=+G=QLPJ1!e0+g7qp_wHtKnsFveI{+9Eo>lU&rmo@#l)(!HVEW=!gLl~?A=hAd^MI0rgTmJa7?(_c9{7=6c9VM@4Z{5a3Rd*28 zl`R>z|LW!tc*+mwVJPI36{mGlz)(HAPvp#TVhue3!~rL8owJGJeZa`C; z;R87w4KSt)8z#YjCadz&igfi=Y5qbWc?-hT5|%FEr<6;*39_~Hq+YpZU^>eS)Y*w~ zi*sL>y>;Z~L)OI2GVVJLg`e}8kGG#C@&Nfqkhy_)LjLC-D*aVu42U4);IPHiqeE0^qK()E#-m)!t!c8|PdKA!Dfb{| z%;f==o|i8l`hl%U03iwb86mwvz^FZ**uGQqq3DI-+xgC{P}se%a*5MEBB|!ZHjxRz zn2K%GoUh-&?cqWQ>4Mgv898`657wTmCuvsUtY%^`gYl>3_c_sxWW91PmnW3_7>+De zIg(wndZr29=85Pk2cT-&2ns7{*3JsSNKHR__KAWeXMA)?h&oT)J|EN{2|l10J*o*j zv76D({-cyiqv#$t$4^h2pbr&!@%@8!EY%$*Ic6Hgi{U~_lr^3&0Q51gQuDsBt4h8r z&sYa;EWiEXiZGi+12V*qAckJB0oP6?PyYOT`VGldxV9WQWb z_oJbYx5W1Ee9*yaK15bRmkU<8hwW%__T3;RDKX=fQ@Xv#pIMa?96q12Y;<8VUX6pH zRSSBkcTV*t3T?3~Dc{(*{nSDnBA(Om>;tKSygDX}E}xoG2ZWFAheIijeMz5nii&NI z2IZ0#@LPs~g_`@K^dPh>DnDLLIBlH*Q1xHf?jH>4pa1^N(jTyck_7|UOT*Im<<(3F zxRP>0!^4CBD@@IV_L9{ShXey=sJPmSMEB_kT1P?at0;M)A~4`BWc6k1v%s4xai}t% zA5wwg!0|~WBAaN?d>h=#6DZ1v7!(ckMXL<0w~mcDd1A{4H_v^uk&{yeP~V-)V%vZd zC%nk4Yp5i@r56+aS3Ogm2F0=`t`|;W2j#$KKzN6x(jjxM3ziIrIm!}+jSuj?95|ec zmfN1;Fl*1y));6_bm@JR6~Exl3?6zFJEmenhLdyFT)%-+jVc`riyCkP!!#BaxL8hj zqQ($&9m`9`CdQ||!*Y?QbVO8}Qg6U_v~6G0EvW)#;Hmv+0S)o%WRinB7|7gW{Ku#( zqwC$nFYMc@Hf6$-C{kbO;l2HuBA-GeL_0o)Cg41`Rf8}SJ$ORFtgLP8NMgJx|N)x@_qM9Hiwljz9Q8ie;60QcnG+lD#v%UCL%<0@nB zA|akStKu9DP;X5@l5tVrro;V`>1OX+7~3l2vO4r1? zd>wjY9Z9M-*Yf1Rhj0hwY9N(jVz~HzxbJi>L~A49F0Wh>qt2xe$~r{XGJ&w7-14^R z(I6GktCpWI54WI~ZVry$_Y#s|4H^K#$2eKBbb3(pPaA=M-itOifIM<->%|wRJp}1< ze^@PlBQ2uPxmgd_Ag0paA3E^cwjSz#j_gzvp?q?w4B;pU?Zc zuJbz2<2=se=uyY;Y-%@_4%WJF18=C*fiz|{_wh{Zo`1RFZN9se`V^b1UN&Qpp?l~_bYiO2dX!@mF;c%|zMXtiRdpEW zpBF90h>VA!Ndx2Kj~9znve?M$t!1?d{NpQl6mXr^Y#3S)U@)AO@#lw~8VWPqmrLms z;xquHqz5S=jv4fdLj4*PMA1_yCR)U;+;+aZhBlGx8$%u>Y>0VF(<_)XWwVi5+i>HzC`3j{*wLp zZ*Bgnp}xo>kHV~YNd5OcNAU6r?7C?`!LBxCRj^0=&EMkfi#|pRv+`Xstj0Y5vRNk080-3^mmc z=zDF56rrGD-3GcN9f0v06Ont1U*JcF)nC+P2D1EHR?VBOcQ&}mgxG(*c(Hb*lC@Fk zJp1pg_P-?v+yw>-&!;s0gl`O9!d*ccV~+FH8kK_Kmk|wEl9`=}2NZ$dAiY zH8emIjVU-im{O1)u*;2RlUt6Mfqz#Mb+Z0&K;i6i&RF~Xu3>Nj&u&hXq^D>bdM2%K zOQ5}>qP<4UuB#1i>=M)Xt3r1KbU>zw_^qdX#;QFg?IwUGhaL6#VDZCSfMwN1c>mp1 z{}=rCS2XjSM<{&)bK$9fuF`)^cnP^L)&@bEMMOeqN02!uUrT**F<=jmrpe9v2hwYT zsn zzf}>)E``p6lRF@>#ug*f7V6=xr-z; z5{>?qCV}PfLKq{CFA)8gf5DQ!KB4A8qR4BfS;qfLC-};LR7yNu|1+=U8ptYF> z@a5UWu51HsyNqkp`So6Zbgm`P#HLNi2O5xXuUdMvLGX&T^G8! zA7vr2N>hPGzzjqbo_;}4d-V@_*HzCCOR`}1P^dng-)0sgA!L>1q4G-J!#eGT?h}Ca z-oZ1-`K-^Im+1kW%mUM%tNcK_Xo@P@fs!b9wjAy}uO~W*2fJ(Rc=bg-OGKN7>VcrW z`1XNV_E%*1#sIa2ddP>N&+udm+fNT%L!6dAmYDNi6i)?c^v+2yv$z5ojMx<~IST`H z(kk?J22V+hg~s4qr(8xRxUlkitC4-Y*L*#%-9*2Ef~O!p2jneIN3nDVCE0Y}E$ zATB)vLG$MIIhIXCCt@(Riegn1jHCxm-dhN*h{Eor+JA6fibi`kb;29P9 zxJUv8^wW6AXM=|Wz*;o|4z<@V*P~0v{GUR_vRk7$GZ4ou=6fV`nM&81T`fOvy!iGR z(1l`vw%e`zAzwyZq0sO-tn_Zm&8tJ>9h4*26is*M0!n4{DD{ zDpI{{Ltz+@xD3ssX^)n6+UD%dH8@}4?H8Gv)q&X@`j*NqJRto$RMl!d0ojfHNE=}C z!G9Oe|2=wz#5yrsBn0Ygw)FfP`F{O1A9B&6tAWH-GR+j<4Rq{u8mf6hRANiT+JVT6 zw&d8$`RmGwh};~a`6SY|wgL`=%x6&*H^EEJQ*XO1oP_}?vtjq`t94~_%tQjC$C2Uy zM6ln%)B*D;6l?@SX2-DmsD5PMT;JM-$if`G2*6qqvvG_lLazkc(L%l$Ck@r{wlM0GHrN@#|WC0|@mVf4kB`_=Al7K7VZ?`1=kVWc@}!Vl;U z>-yia*4J)zjf+l{$!kwI9p}{9OI{Do@)l} zq!sZ#Xsx7$BH>Ibx2$rpq$P^{0LN_upd*t`%40;zCkf`5Z-WAfB*Dg6FWQ4-MLbMI zg|2|QUHo?K@o=J#sXBx-m8Rf3YQ7}U=s`07Pp4%X1K z+BdjezDgq{^PwNiX0G8spbTzwg?+>KfdXWhU}c~8!4g`J1m;)&kr4g5z&E7fa&vt% z=k!P9`NEH+ASO9OeM7Xc<{2P2`V($oQe!N9EW8%#xCOu~3xJMVbMaz89e#BEQ&g-% zcbXXVnO8~}(i&XBnJhI@{2TPMs$j|u_EAQ;isZcHsUo=e+Q9Hx#D66LiY|he0T0qf zxI+{|j@@ph#kf0y=@P7R>0#IFnwMqA543KY*!y{~Vi>BPTs+H!8n% z9%vL_GuvZ5vh*}1wt=xT2E?W#5XUGIca`BKiSPaedM82~z;;ruih$zT^(hs^@tFB4 zo(N~u?JxfDI9YCXw)!={V$`1( z-D{qY=*Gc+JqQE4sFStnPhvLmMZCvI5pp+d<==6IU*~5FIaY2qt@zTv`h*;jAq%J@ z0GC%)7Q%aV?LO@+5Pzf)86GIZKh@5>30@+78(reVco%ZP&8fwYLGgyAtN2Wf3biQ~ z71*Cwiu@RHz)`43MYU424*v#FQu%uU#42;y&H3m)#N<11)RnF}Fdh&OTENxzWLoIT-$|k)lvLkaeE9$0Taiv5k&+}Mp8ezboxz-ks-&lH ziU(7Q1b(-*(3Prpae)2Y0Sg#})rtr%LB3IP1kFgVj6@h9P?cUmBmvEGY37hB>#X$8 ze9$PP@BE+$Z|nZC#3|k|E#T2v9v(*ff1fO=R|6Pw)3)>J9QbjB!R9foSR~#og24|-sz)Udk}+4w z?=xA96y3qN6H5PD4PR0OH3djj@Q7h-BNGE>#0__sbZqmB9V8ZuZu;}@o+5D9c4?=r zU**%7SNtIpAzRqP`N7R3rB_Ix4WE}u<%NHh=V6s~u&swjN>Bu~L5@bggw@5HZ=MUn z@~uk6eF}E@3b}!6Yuw(^JPglH+3>RU0d}TZFL~tl@{siE>u-g$5d;l!8Zj0@e=(t{ z?;%BNfAquZ|4p5qqh3Woo7=JYJHLZA@YjeST5~Mn&P!A-IG0=m*6aShVu%%h81ppy}DQTG}S}H<)QqW?njQ2Y8Y3*1bw}Hh|6xV-^;`P9%Dls zfTdVhN259f4^YU?Zcc#ya$An4Iy}!GpnhJ&no9W+?Z#ZVx-P!9$ttE7R{bl0<(SJb zdu*xK#G*VA8PUtT%sFlxY?=k{-s``CK?r2n9>KRBKP>$4wxto6onb?$$(_OvAMOBM z2IWE2F`%Wt218AM0-C2>@P>x7IUbgE(slDSl6ri13w%1Q+l}-t#K@go&I04{! z2Ig!peRh_^qniYyTtu;^#NeUud2w`)Cygo8w{I|HN0N~LgJNsJ)iP9DFY$i_fF!DC z?>(reAR$=Xrg<&(uWHh-$V-Rk0{^mpBF?716KQ|q%smE!vEKJ848fcR*6<2E*rBJq z#b+Gu$xuX#plC6kAR@!ZXc?su0OamOacwg#! zz7$TrlT2g-eGh6p+4f#|dTa;|1w{TR%AICOEl3x6!zqet^QmV|4ZrH*d!FerMI7#! z?!D_SICh5f^BzIksqCnO5l2#K*$qC^H=ddILzLkLnpQ^H+yxf5>kjy`{sCU|m z)?aSD=&MJVmeIG`kk(GjAu1-p5b3suG&MH}iyc;9DEX*CX!8aCr%(007gXd40%11{ zZy|!uB3iN{cvU}VJDxO`6ndK~f(}|ZFpWt4C8fj@MAsC#rlfAnK@J}Zv<$MwV9K#I zYC%ouq>ap^w&!CQEj_|CDIG8IX~zmHS6!f@^2P3nSiUvft55`G+k55I7sXE;kAdE$ z+J8-w-T~1Hp)k=!!Q;Sh!48nGOaob8j1v+Ae?$;^I#{?%{%`Q-22=QLep?co%BL+Zk#z^`P83j6g3MeBUzK3?GV)ImG>v=Ocl z@N!AqewlO=G9EH;*BUpjkB!=dvZWay)OtNnRE9~IRSCPUH6ZEuB_ABd88tCSPv{t4 z@rxdBh5DP%h*SYXnJL{q)wgu8Nn#R)7)OM)R+`>M5#F|C{T!>$RiEm84#qHh1S>0a0r<1gSPZcGrQ?0*8o6|Fa!LOal% zXn_cVI>@67qHsAUFJLMibJafP#`Qi3vrf>;5)Qf>#UWnqu(tYUjinAa>d;@Bfh%B(lz?PQ zf(?TU5-vIDE15kIzF$wF9Xb^PD3}V)31a200=j0bh&L?+gJzh`>%YPVBZv{eZQ1J! zlhI@qYMg%L<_!1K4}gpYO!mj@W1?X0dGa4$~w1t`GssWTZ zh7u`XOlINAR8wt6nsfL~{sAIa5`zH(2m?GAGX+~>1d~hrj2EL3NzFU}Q;4BkTRKgk zgkydvHIXprPn~U9G+vQ>P-}8IWs?&4>ykyhAH1tKHpskBwVvxyPk*k`k@Aor5H>fl@qCn|+&=c|*NP6n`bF|-| zG}gJ0pb%BX)t*|fd*lO#hThImmFuQ0ib%D@iMT!ZCE4|&tZoQz9~U1h|4Ju_TJh9O z_Jq8(I-YtZw0FwT8Qh#RkNvBJ0s#XGUG)Fs#?pr@gTf_F!~c(MWDt!6%mg=Z`@M5=AXxx2PDLHO^ar0-4K#fux>-RYC{6y&uSvvC0%40MJ9o zO3?wl_2GxrKdD2+sCyZt3~mq>ZyAl1#M94;J>3VBiwkv*0CG)eX%31RsA%q~iPmO_3@>U8!zT>A)p-M;e$_{ndL$IJzs&-GF+U}D8 z9uxJ~D+0F7k~hD$4mr62VK03CV)C_apHucyndH6_w6Z$ZtfZX#L-2&XWF3F?x zepu@!IA`r2<;_qu*Bahtc>8Y&XGjWSJ$(aU)*?sdlLinrwswGX<`O7OzzMTMjL=xR zY5Nh7P{;{MBolHGDhXV9+b=O*nDV@QOz7|9fH^6vO? zX=w_exJUD%_}pWJQqr8teU%iuh9Ch=G1svTlW?DJ`HK5sjh#zTuJ!gg$7BZousEe+ zPp|^dc6~Rwx@Qp>^Q(i$lauZXZM0YzDQf56QXgM^JdNB*aQZ#D9zH-v5jvS@P7tnG zu(phTiSzRGgGL(gDTYo?ebmSwh@2=szkM%6E1RHRqYxcOBYZbJxLR=Rn$t&|t8-JO z@tJII99L-X5alp@@L^O1>0>2j^XyjC(6ze-5*3c@k}-dE?WtlD|J<{Wi+`P!^aJ!Xy^og~5cFR!{Pi-54{$Fw8}zL5)~E0`gT* z_OT;%yV|4{&4*ovtMWo{rB7u2)Ch$4^gSE13B^Oti)N+`uyMX8`XbKQ$tWI$-zK3< zat*^d;i$jhaW!&#WmNs?WEJJnD~qD%E_+}z^*Hrie)G3FzaK!DyQn2fBQ~ zkgGMNUq9$0!J~D+%rz*B<2|P9F@sMD%hVf99KkJptnl1OH876f&SW#gfUVYq`=P~$ zI%d(gxyL~6ZUF}V+^18>b2koS)<>qQ(XFpMe`E)RZQt}uchpxmhz4ITU`ByB(-a?E zm=wH5%N+l&rjVErf`lkeIX(EJuVsY?tArE7YR^53py1w2phH#w;9a9tI#|CngQ%xT zGqy4M&kI*)=2c)~WCn^}kmIvpxxt`{y^v2jsY6gp*o62Zw25d^TEP)(c)S|$V}0f^ z>vQ=q#y4Nr&F2iCViPmZD2tHP$ARNgFYN<=^p_Yd92Gb)L5Ubo93ik`(}m@UO05Lf zpR&P6VmI@YNO~B@63azxX62n$J`I|4o6*nu+=(f2_K?WS0xRl|C;3X|NamgGbOPx4 z4B$80BW4)U(EVS3&}fDZt9u>L@mGCMzgx-_b`caR?D-o!y3f!p^Kf;qU}QO!n2c8 zW7e1aFL};IDBR9xoJFWVv=2~8uq9JKd`Yu8{iA-3mlp==0V)~J8^UEe&Knu<&kEpn z7W`TnfnZ?^wE%DG<~PiU0~8grgRpjx_R;VHbl0;O@gvYEoTp%VBtJ0lAvc{=`)x=V zVMJD2`jxm8xko*b)N^qzLczCvz=$(%o4@AF9zM zxk`C>Sy_aG3!XgwFcuC_>TV(FL(i+Zyg$&{U$O59TB%KzkFx)&jsN+0Lk8BY{VqTH zGBr}7=mTB7^%_u{xZ=zZ%>am6jr>-x}OKGFf&*{F^b-Z@#hwJ(JVAY6qD{&`?IJ1o@kv^JTW1Xz@q6hLyq#O>`oK$3uC^Ow>6_V%etYoW3M;u;<}ISBTnRElxQ6 zaIoB$5nR!_TPVR2^8+kSJ>*T*^RBtubMM{(nAytM%z6;bZJ^6?+_IvJ#Z>3((#dIr z)>B!h+$4@5WAw+9Rh8g%HCQ5dd_fIQqVVoPe1;3HF9(;g``M?v&iP%JBVm-io`kUK z){TG;*qV!|CUriAI}j7(^S7wIXxATyKX3Ylbl@G&`*JQ|^*hwFU0(T! zb}5USUymai`*Y{lnqXA^Neok%7nlUrBNk_Ld;b6V1^)U>>=`&}wYZu7;h}4jK>jtL zmkEPf$rT>kPY!x#z8mhdmxYh)hm9hoi+5pEY#4ZKaou`DXU^47uPx{wKVJ$%GQR`a?GO-6LUXPZ=Z|^w zADr;$e>sEeSB3k+ZM52?jt={9xiGkLdq69Jo2a-9{x57Nj*V*L^(eC|<4jGQ>tmA= z_Tvk;xktY-WyBd^eTz_0#;}8&u_~hXhNHa4B9hi2deGffrz&~~Z}bvFJMm7{t+N+8o~F8Z zjANfC9)E&3$Y=5=>#|?GO|m1rlwFt&GccH=z8|~I{bFk9<&`s8MlX??7T-hj_4YSj z6`GAQ^oof;xL39V`WCu#BHZ(jY~7Xwg0oDGYxbg*#@$NH`Wl^gS66bTIl;_&%I9EA z)Wmi&4?KRadjHQ#ok6jN#h4b;_|1y+j3??@<91ose)Y=z%Z=~aP^Q6G7-3b7ZzZZe}6KQy2U5n0o|4G?~R+9kXjm%$c` zCY*O!54K9cyXtK8AKUcnkXpqWx4bG31D|?aD_nA(_PXM)_qB$hv|oAOX8$e88|RrZ zj@9nwZuRMSV-V$14%U%82Sdn<7eZKg?JczS5f#1i*TDD7wpVxSSdp_aOr@vY3Y436 z+nbYzse}2ZF`fYGa6bcS8IAxr7*m8h9@@}_6>aNX+9-i$!{x1b6zH@nsZ#o2*H@?9 z)>_@~B-_Pfg;tYq4ji00&^&61(9$LxzrX&XM$D(O&f0eq<{(_4T9&lTIppY|3p48p z^o_CL$kcZH<_on-VUlr1m1@p045JRi7z;|5bTvDpBeDJYv@by?gI4e$hCs#Qa_Iws zK^=Vt@cR=R#^f+m7{18F;PdCAgwVq2;rB|@`pq@A^)}c~lU=0GxMvUR^-SoU^PWCz zWVxY$33FF)0;KOZd9VSeM+2xKggl9&9#s|~!-l!NL5yni`KmIi4G#Ydo(sZIqD-`^ z#oPhZ-)XTxvs*HL1NRX@d&{litr;Wj<&3XotG$UY)O*|Aok?zqWeF?2N<1wQY2PkG;Em>GMZ zyU_8=OsLhbmT!Fe+)Mj^>^K%!Aid!UB0Zrr-ELR4)P*umr%3^amfa zbT}26pU0Y&?TkDjG-iFh{ps5a@|clhl+}DY13kHKBZ!MnoaRR^LN%q90nUM3U7lpk zY%ZHny&h~PqRt#Z^7J_EAU@T=BW8A7=ePT90l*#5vk0QyyM~6=-|QJx-$C5)r?6Kj zmx3?OI3^9`=sW=!Uv?!lajb`ML_^cut+Dew33{l_H0hp;W|tBYU@orSz_XQfdmZUT`P`vMuoLj!7`)s+;BoG9rh({E6;KJ zQHum_s7RlJZ1QHmQH9ma8yWg`v9Dls+^6-kj>wDuVVDNPAhN@*Q|O%H3U4X(uK9v2 zn6+`ltEa&YeW4OxkeO-U;1ss^C^V3{6~L_$wd!<}$roc5vABf`Wetrg$35p{#nJ=Y z9CtKn?FtB>U%VhYemuTl<9-Yq>CbWm!sH%?1(e=l>!v-c8p zl*)l&Guhw1qyL2w8BL!DW z>yR5ir*&}?vT_S3UK}K@02jIt$Q9T6o7BOz)L#m}U0F;Fx1~%@Ao=XBy?Ej=3Qcz5*jECE&EA0bQ7EASESFoOcI9;HU|LHMSojhCx&)L5bW3 zz)4XMp!FJMST=dx0C<=1hbMs$t3(uax?vYklPzvCfeCTq$neS%{B`l8yi%ZHGVxoHwD ztDP|pz00#JU>4_?GhA%AFYD{h3hD(RjN7%>l3}__E{5BW1V3uKRf?I*n;KUk~Vy$?oj( z15J=cn?ZXNQ(O9*c}KRiIQWlGqam?BFYupr`Fk;*SD&e&@VN9C|CjsnUmvE}VO2ad z*D*Z}2&tn8pGj?rMT0v7cz((_2VyJBZ{^rF`i%ZV$!iE1<}> zVjZ8$FphfwdS$icSUuXHAZb5hmSMDI1&zsm@3hr>t2J@tPfKgqs*j%SG6~{(F*A}H zDU&KEOF8reif$BcmyV7nV$POw?`bo_%lCb&=ur)kwD7uiDTh^^j{R^$C*SwLFFPC; zvFx-xj}!nyk^gCtN&p_d)KPWZ|D2sBYaW z1jg3ic51QN>CAz%?_%m1YyhdyvGT27ZUU$JunOeBT zNwnZO%HgZ8KzePUEm$@g?{&?2a`UOt1hXHI z5tu`gP!-GaJmt#^y(mHVbK_c%c@p7`T(4@;9LSc7!Dk=;K6pXmK=+a!SE0WI6~K7@ zmd_%9Hunf5S^e_T<(DvG@-ufG9D?dJo8nqJjt@(&c7jhX9#yJ6m zzcP7x;-IwKnf>;mi3!%S&v{flMlDz-Bf+9L(%)hstf5#vMEk4f(*%FJ>spnNWCMpe zLzM^R+NJiMI&){1qKbL(%14&N_PMbOCT_HcKqwT7UAowmk>H$_(P=4OXb7orQy&*3 z+eK;e-0erZj_NDKRS{t9)KWa}dB>@&?JPQlJ|cKm!Q4Oh zhF0bQv4w@T&Xjuvo){==e=7ee8WU1xSZ`fc1@h)o0&F< zgz?)=rDUlp%_$1T&s}}- z68z58yz(?rgtuPVJ-ZH3cCfZ*J9q7ROb>k~g>ARE<|OCc1fk&sr`hNvoku#kN~Oln zSc`uSXju*e7nQ_OgCWk!YXusHzfLGwdyjm0xdH&3b1t`|;h>L3fA|RnoWrW7wpv1P zOqvQi7KX~(4xZGN?M*kB6vyogFYJVhlbkdP>X;9OoiHQocBqocd$KDsNvso&#mJON zq>3me0yp`><4za1u)!1Hx5?9FCXrd#K0~f@CcJRj{W;N7GvGwxj;SW+K;K3vFCG>; z(;OB$T=*nT_4ltAF0CNr4(mvZlJS8pT%s~&@nmA* z+i4}K8O%VM&pOCT>MA7BW`K+N7?(uV1}(rV^=uD#i} zo%uR-wm7oAsFjI)D~S&L*@w^D^kVYVuZ>m;0YBcS%_Yu@Fvu_tTsP3WIjEag<7;L6chqb1&C z+P^USP9PZUgh#go#rhFr_Z_HE&)a`+dp!Bjf0dVL3zIqKV-5g$;vJaVkMwhf9iUef?(T zc?6)G+l@hNFiO@yjB#X3_$^X)qe9+|`8JvAzoq=^vs}1ObJ6Ag3C+lL8^Q?tyT|7; zs=sv7j2FK8^yN4akN? zdup7zh*KzZA!`bcq0dQn@No&%M#z?=A&}o0W25wY(5C&-TrUtY`<%aUBzvE#A+3E9 zm%dV$cyn)l7!xWUhfTlA#(9uU7RMDDT0vyUkpU`=JJ2yN$L)*0CUchA&9bFf6%H(} z<>K|-4fyzN9%ihJ28ai2ulWS$FU_YhZiY(K98ap~P6sP8ZzD?CW-(hrhQJE)||e8jn_sFawc z9>wQwd}sH-kJ3JBO*qvhe#oSwIg{*;xA8H;Fwk)rw@Y4oW2?Au=@xi?ZpTO4+U9bD zuVr7Qw;uoWOWNfrZ`_?HKR@MODQ>*2>IB?*Eg(77M&$y&jK=XLE9QlL~K@bqYKTKkm#OOBt(WWqUBf9 zH6Gie5Ji0aCbpEup_4OKaS=-DCOJZB4ikWDivb>R7Cf<$we(qos!hVJoY-N^+ArQ^ zwG1;hjj$atMgmGpSDznMRxF4jXRw9(3h$2fZ9UJgXr_%H{`m6-q_$h};NEq}5sxmW z2NX^Op92oiT?~Qn!$AImpB2SVOTV3;>)4HcP+rn!=ibVcQTibpCVM*TN}Xxlqa-*O z0tuF89>4^=XuB5bn+uBOw35i|Lol{F!YiK6dkU4@xL-qjtQg^ z9swRiY-#A}SEL$GC%c}?_P&;~yhO$7s(BB0=46@IouN%7v0ZJAHv(SOOn) zD+Siqw*?E2`j`%M_~xl}cXeWzRI9J3AO#{dET{O*eq?2TKZ8>cX1{OiWBz_z2_Pk= zi&63x%f|@Q=8&<9vAAXV!afqn&DVU+jIBhwgVE#1Po_P!RVN2e6fgNvIcGvgyGP|x z5eI~%QKz|h!Wd!!zJvKx!o|LiQ80A`T4gsLBGAi?tTRSLEEx_Y& zP&Yc#w0s|Ys*A72f%vU;dPjnVKK6heip5Sz0LoaFtSjP53=i!5Uv zGN>tcAAtJXxr*IdH!T`}ZMVZ~I4ffX<+WTEER0|nbAleN; zTkMfe_X}8I(i#djl;6^(U#GYh3C(l$4}M#J)dG0acc|PtD~HhT+>GJ`3r0c6=LyXj zFv6~c*|+4|B)-aj@DF$-OLWf_nR zWL8rRHy)`iwqgTdq4!v`^g9u-4UzCI*%XZ=Qc${0)0e{Q#UhsKr!_4nP!m^uykePN z5(XFH^M292_owm!UTn_u>{Sqqn=FkJy>SGZdAEo>MBYbiCuLLh4|wmczLn*egV<37 zF?DeK^`>Txs4yOx+AHR$RL%Ay;}_0z@eby}!aL22R@~&C7q;o$9m-&mD4w^tV(G0L zeN?EtU$HAZjFw2-okKG4VV4N$8~?Ccxi#fM(yxkg`+^dt$cT!AkK-z5wwXbuwUi`r8DuN40&4TW@0XL#jZQNGN=7gHE)@Tr(Cj@mgVcRV9E8pelYvN!;ynajH!qo}5 ztSgzRLH@a&j{LCKpndQdu7VQC-CJ!~V`S%EF(j<_r&T1Cd@hbDB%}v5)MDrQks|i7W_yW*A;H2AD$=9G`09L6mPNvibnj8 zM+V!j5rtth$B4IBHmX%`Zwne79c!CcLNl4KS`M+qyx*V>NBxWXbNwK8?C}5H4s;lJ zzX0>!Bm6Yd8}ZD0DTa?3_J~8q9nMPv0$zF+=v3J6K)E{#??0+LLcw=}D|?s;n)YFN z(OwsvKiCLJS3l9h!Mc<7V__{T_VA}@s;ui3ntf_o#Ewz9}!@vh>v$PI& zyf%dcvIq%P7 z{!82Z5fZRR4kC5zHTyHDWIX^Th%|fAVcQCb?&?txK~V>6K~n^?Qz~3=2w=%6t=)B3 z^Tb1w9FrdGgGy~FC+i@4aw(^B=YbrF>7e=Rn6J#4+Rh7UodJ|yBXt9jt)CJfN5oRv z57pSNg=Ni4lNR7IKa@}rAH5fR1|=L$dqzQHY~ddbEmXW_ZMfEE?ag2Pci)Sp!B%)5 z_GVT{PC4n3H$OrhgQj2&QmvrwBFqfVoAWE(3lT_C?-X?e`#>>@Nyd%oQF2Mz(#n^G z2tZVaK!bvSr!SD#IWHf~1~4tei4o~m7)5j6fmsiR>}QQ+CPURH+qpgmj}U}kK>554 z_*0lbhoRg7{Zy-bHs(nj?+XT(?(WKOFwVB{c|{BN8G1Mbg+)ou15@L~y#mM#hJdt~ z4GG%DUC%Z|4PjISycPrnk9o-IzC)w4hIM~uX^{QncQ*i=vmp>e zPEqYttcB#$iD)t0*W)4Uqr5)Q5sn}*Ew?6fC5jJD60)U)S!4>__CNg)h)PYA#>%OJ z=$F2>vCo`_*S-(vgE`og>|d3AB8trLK-!e{>9u=P`S&0}loI8)1TvZoRdAtX*=dF z%~`_C7{6#lQr^@*xu29`6)Yh_1Ob2=d6lXXMgN=Pt)D`KD<$|nk^lEwmH0g0UYvbt z4!W0}u`$O~s*pld5xyt|EX*R{dNIbe>@TmW*m}t$kNpp#K`6G-QVU$UUIX^kx)sc{ zA zYYHbN@6JR^XhE>qwov~8kW_q0UhoxB5t=Z(YUT18ipuySB&M%5YU;w_U!D$NF9t-B zu96MP6BmX@&=pqP+adhG+Nd8yrJ;(!2tNHy(yzvM40WsPP&b_FsgL+3r#aS4tl77}=j!l&zX zxSgdSA>39$JNVW(UD-B1 z7gpwxR1EHl-ZJ`a9$TGYn1Zki8nhEo5TkQ9zM?bf-n2Z?dAtw9vIUU54(R-F9nd-e zGE2NtqM3Ao=Ij0pFIucBQ+pQJ%Z)oQkHI&|3&VMHu3slR@^D#9F1N#b4UBi&8TQP0 zk4{%I@|Z=XTTzerXP;I(d-;6I7mI1~6r9;i4`J31B?X7!fc^{AaOmij?--?$S#4En z;)a46#`wO-l`Z{s>n6%MSH1q&$-e8THoe4#$J!9DTYMj{9?0*6l<#D%nL$HQ@v?Ep zcuzE8m<9-sO|fkW>xwq@UXOA64vY)yRg>DMAJ*Dp%?n;FoT!QxZp(cMb}eVq7c{i59d2q?~_FHrGj&b!~| z#7853o!EFIa~aNT)$W4$k_vKL0i>Bx(3Dsx1Fe_eA`%8W{YuN z{wN^a(ln3yW zuKNv_+k3>pOR7g9ufyicS#0N_ua>hBdtbL-93VK?)2e-AMU`|2QkA;juA>$AGGS3N z?-8fZT#ZUSrpKQ1;b*J1<1&s(W16=(b{{QW!uYNE(q&$Kkg(ZEZb>)3_byc7S#7qg zdaJS_n{j*31@UrR96}K_>LLh>o%>74r+Jf!Fq^g3o<~xD537cPvcikDJOhhaUB@om z0#GvX4r%p7=Aq2T5aI|uBg;w9^h=|tLJpGKeqw|z?~=07K!rib^9Zz zo)3^M5J>vBCHvJA;-H53=ef&TIl zc!E1{%gPA6MPP#ki=G%WhSn4-1sH&k$GvK-wRQJCCpwe-TQi!d@8NCI95OCSd@^U* zJ2*nklrtr9sTalhgx9n3UI1CDy=Mo80E?ilA8q`;C4CuyPaY6?C5I`vg@>XJOtWw~ zy!*PW1vWNTSDrN{9&RldI!riaEYN5RGp?{jpSS7vGoJ6+2J3PIhe%n`rIpQi5Ckq) z$iAUoL$NIistop`A6 zaF(Fj$JCuac2Baw!XmeTmKi3EIc3nQAlKn*J*26`DVU%aYfM=VSMs^KE~KY{5V;=q zX<1f&1sownDHjW^flEc1KCM63Eq)Vzkuj1mPc5)SL51gcSj^JB1=-m_vDuFNYht8u z#9)ELmQA$udw~dRtXh7Cb5<2ZkoJGeAVLtp>oM(sSdtQo5h19GOgwf4#sEKVod+~? zG^3wF0){Y|`p(&wF*|d7gMP5RabLG+-aD-=`!E<^h@H6qc_BSEy`s#-4FMDx_Bfy? zr1%5LdtjIYTK(ngBTsUNfdzZr{YuLL^zajO04VD`7vjtkgu)+FCOueL@#u20b1YIbFJ*mR7%z4iN zxZW2xq37dJWjO|DI*D8nHTW>trldknG$Ekryk-LdO*Cab^8J5JJUU~G0K4xss0<78kTT-}kix-=)l;@WK)F*m! z=RnPpZ;;%Z`^n3RgZSK(5+zW18E`VagIm>retkMv(vorDTq2Y?(O+}*M638yjaF1R z$oC&NjB}k@6l3IcgEf%R@YYkM6u&7aUOyiw{SZJ5D33R zqz$-ke7p^Dpajx7)osMti9sq&Qy_))XBBib;xv`Erp6#G6#kx@CkM2lyEM4M?PWm1 zQd@Cf9V;uY!x2=y-XtO)$9H3?+sn91Sxkl|5xUxaTX=VsdhAj*l$w)~xr`5H6|_No zwVVphhGn&M4+D&Ad+io*M$9xhaeg?{dx3&rFZAnYg*0Lo*WOK#R?j84jL0&?|7vPd z#2WMV+I>%Wb_5I`ZXLtn2!Iv^IGurV(o$x>b>GI|I zdFa$b;Q6BS+>4wfxkL{3Zs(xgobkhXrC_Kl*P~RcG z3#%QQCz?$ktb(W>B*Og4W`v<|{rG1m8=%ystrYbsq+4c%{UlOyl>DAc9g?G1*&vXK z7|0@5WOp_?(e|y4O#g|a6KH~tB80vp%6q0VgEJ&r=HIjn2%6&;=(FyOznYd!B`vwWC9)pl)+ zjLojn_TtCduG*Q)ekq;?X{y3=-poqUrERZsz8qdT4#(~(U#zp~V$GyIS0Kpo6=$uf zqXiNN;xNYu-xD9yaGFo{Gl%)%eCaOramhGnNhFl#VgImNY3fv?*?@h`Rvfi%2TiC? zP%+s z4@;WBS@EzS#G)7HrfHzSzxXK%7?Q}by?SKaAaUM?h*`Om&i^NH2l+UM(YA@N7+^lg z9a;o$$_1ao;~+V9!EU<@j=WZ&DBUgmxEG!`*oF{*v0fY!qqz{RD=TZ5A`IEW%}kw# zguV(lnSMPFEm0GqoZjpLcOwYFTxiw?G&Ysp(~y&GX?=a)$C?mB9P)8a-oEBvr$DR+z-Oj%8caK}gW!Cvr)AfEltWm_2QuSO z^!pn>TJf{-j5y|i-$ELI#fmjA>bX_5(!$*KW5glRu)DN=;8z~P`5_{-CehNvA7HDH+ea!}c+yNNWRPyu*i%-*_0dN7( z$9R3oZnp8WuafL)2j-xa0o)505SloLn+76M1K@7*Wyh8r)JGq|7_rKf@qavthM_#j z0Q<7}#ia)wVT|RjX9O^$$F|n(T#?y&1bEu`TGbZw`cC1!ePXnyL{EEJ+1ac1Qjn&X zCASGZ16mtwt!FD0T{_iQ>d_CzSFkde-lbC~7!P8Wzko)q`XMOntWJb=Awd#hVI@at z{Lyhko|6~9_PO86Z|abHkbm5{dS5K*FDidm5Pq}v6!cPxRTPD}lf9AV)Pi>?Cb^O) z0`Vy+!B0T@(e-!^hIH})R~vQ*LU7nIfJu!`^E!ohX9}dq+V|l}$B`*`1*uoh;F8^> z%jExf07D!j;BkRc!Ve_LkI?Txs3TmP{>J*4%zhNlUQz;sNxZ{sP!Yl?(rW&)QaogU zP0peJ9GRB1iZ?J<()bQrYD>5XSc-x)Iq$=>11_Mp67!=3CJ5EqTz?fLzkl#Ny@bdF&Q0Fp9X03dGhaqf zvj6H%YNUTcU!#ud5AAD(jn|kmK4SOI3bSUSyKZw=&3{}+r?gdaftdb4;g+2t0j+8Jm{-vcu`a&!CM# zg$$)nl@FuZAR@U4dsTmOp?!v6=+y^tTuOvw9_>`Gwz5A2@+N{?-IBs~4OK~2p%Mz> zKMLB|IMK4{6%&>#TDb?c4P&2{zqgFl`vJXBBe1{Pd2;jhFiaOLquxE)`;I$lN3^Vr z!5n&zZP5LwO!RNwYlFJ_^;4j|a-zk1HEK>)aA_&O_ifEm2*LfebKI(fPI&|4?MGg% zF`-cs+Xo>#>bFwd9OdAc|721cQY;&H54K{@X~*B5Bx4>#cof_-6*APX;4?ZtE*hmk z6d6jpFoYY3=~oOn;wZ4tvk`q33)Npgm!dG&xg&g(L-;A*dF>*0hfS{ZwaffMTU9)vSu{YX z{k5w``+NkautKrqe|ziKXHYUhL0Ew7&K2F$wMig^W*Le6_FUl!LmpIT_griPCI^@% z^1n6Xtz#e>KlTVG$1;4E>&WsO0!~40IFkEH;8#eziiE^>=RzAFGL_y0kuVr;*PQJ!Kk9d^`#B!PhRYE8H*9-~-SE?s>_Odu`171qF=*^78)xNqT zap_ZFy+>xKh=hvFtml0>!|MCH@7Hrb_g~LHUgtQU&o$nAUKgbB<;}|O zTZSqMa8(R&q0^SU*(8hXpNwjHp<>#gj0i|WX~AJ)iLgugrjyxctkk9en>74&Xf4 z-jq!Jb}Ikz`^;b0I)aCer%Rkyfi&Y+KWhK;Hr^J{DU!o50nsYyah_K`%7OERM@!*4 zw;$IT);{-B8}6aG)SR+IYB4?ExB(Wa_g(k3dZz83U|!20&s&&z$LZq7()$^ zdy;;eRYC#5C3MQ26$?JERtTsZ0FK;xZ%7`{h)2tVG1@As2n=rQWe8(@wERO zwC85QO)jx7P^}?I;U|jO;g*1ES5+KuyA4bX;%Bg~y+WmO6+^?hS4}wvGN7=EBig{^ zxIo9Hu!5^+_-Cu#W>qs4pa;D^f9`o}AV`Y^senEOJS~9o zD(SguTu_H-i;2IWl&aDa&4$che=qj7oxZ5ZGiNmV8o3{}GlU#tc%F+k8?Nc~8gj&G zx-st4yRhAL;^`kuFJ#KhAPBH&K+dX7ll!h{<+?4kXe#Xx&-wNtGu_GK6Uo*cjnNkm7Kh*&#=0u1+JneLiyHO8EFIhrj zMFcLU(1k-M-;L7P1s>@dFIclVN`mdQ6R3?rh_TxKw3FVrgAw)VLd?49NWx?jFST}G zu$Nb-!QMU>-Y1u2W)tkrLeZPzOO3Bh4-5{0dPs!(D26`H8`$Pl+yPyi%6^!A5NWgU zudTk9VPCPh4P+i;0XGVu%T{rJU&EW|VvpHDv4QeuMs2^50Y}n^Wd^_tlRgK`v|d5v zRpHuz*svf?&6JBmHg3O2sNr1;xdmc))IJN)47D9xss8D!&))p~w_n3SBdi8tLup05@h_5w zr;D$|R|&7Lg_L0M#e<#pYrT*Aun)E|sF!1Bm79AUF>qe%8&?7I zV!Ifl1w3#rT5B_QdObw;--tUME1<188ti!QV7(0#V}`INv_X4~(BRF%d9+ZYevpDS zzV7Xp7(;p^+&a!eK8kfD+~En-rKW>2lOEheU-S~d9A<^i%O}h`p5JWEhHTAmc;Pc0Y#-gZecMa}(Uj!{AEpS*@(3i&95FYNxf>X_Y zO1?b#r`!1qtP2@pm>J(a7OE0^j~XFUUU2Cr>m|)h_=mz3A=1;o-w%DnmbYEbP1L_y z3w|jyj;$i=qx|ml;-_1?>{GS1PS8=gR9>jX-!Ki-hwBY`M@H-RmmZDI zRTpuaou+hBe8ZQgZUt>^87QeEd`W4Ktkn-yCaul&$MvCmf=d$4Ca{EKd+i}e^4ESl zM45Z9)GLZcA8dQj`4;6XyrT_tl~KHU5CZ9Trnyu)TwCFEg6wPvHp{k-g0MLUmq{G^ zehB8!L(P2ON4qow+8~~^#b7Wup?_-o+=N^aNZDBAaYj5jViY`i0dO{?n2%b>U&CE+ z`T3S=DBBT4`Jp;E^3X{7&ibi0lLCMmO&dlqGflCXxda;h}awfW{R1F37psHnOTTBpqs*gjXwwd@G6WzDjtL$*~ z71hV{00qm5-(R#VX(mCcw!z8$M+cMP^hJB{)$ZzuhCM3#(&{7A3E6=Xu$0*i!cr0_ zRzZ-?l*JJEj z5^qF6HL?uD4;t=G0Jp*en^+*(9!B>99%8 zE`{Rc#3PTyEJ0-AoFQKt@T<8fq6O?)3uv8J5;$sAP%f(tq4!YHs$XEUL!CixNoP!K zm&Qv-zMM^k6C}-?sU7)dsklN7!9R0Q-#!UV1ZLp(rpRq~X~-FmZ*?fy{7N$yuGvY1 ztm=;J6MFOd!}{y7dVYyBTPuZeK@5`i0~e^DcmL=(BgA65G#AlNmpQ=OuFC0mQko>rsRkrk(>C?NKqjx5un7etCJ4NPhNRLc~QjuQ_P{u!QSwnX8XP;e5R>fnS+KZSt*auSf8V zd^%U5&!eeOJ3R=jc$j>mU+0AKU(ihKpOH;lP{LMy{qzOtbJFt z)E`gOgrWcF_ar0{t{riu+Bomk&{AGt6lvdDxK-LYqs25t8=@rFC9gI-ZM1}$<-v&@}~<@5Pd3z0NbwmryH^FkY|t}<3vc9VyRL8(p&erH>w8t z&N)2aaoI(e>}e{GMOOcfm!UTJzFJ*6G+qX5i*!hKN@6K0lVYzZJ@vS`1xm}J_iCjS zAb+O!`e=lQV~L2!8rmYLNyle9=XGPdqmdLw_UUe~F7Xlgb zVAK0`6(_ePJ3j_+pO02Xr~aJEa+U482K5)OZo2vmN$&drO!%67Bwb17^i72Uvj?vE zQLlU`f4Dm?qD;IF<$*&8W)*Eg?#P?1YkG6n3Zh%LrPRi}7Hgi}J3-SPTj%jU;RMhu zgZMzGFo6;1-9bzmF|GARQSCS>teQ~I1^X9pwW=EX1~cdybl9rgd}jQ^;lpoVZ>N!5 z+^UW&0}dauU|PNXCUp;jxEJt`WEd0710Agt*mscR=kb(uB86Q;@6sHsCr61oFejX$ zSR@{}x&^f0nrkRGs|$YHnl^J}wmqY-nMpB~RXuT+s75EZ)+9vT4t*#(XI8PvucRmoi2RNw(7l4Q| zfw54$odZX}>)zo>p#eh*h1VW_Dj9=V;7iuFu|IkL%}5>&_RI8@QxI4dR3L>(w%vtB9n5`HgHYH16d$S0cp88A$G3)nmZR|x9 zg44STEoKX|b8p7Nar`ZiLhTk0f*a;*ph0)wx$%nR6#gBLlK^IGFA6#L8<5!d)^$g* z+Igg4*kM`Sv?2P)l4saQIxQhCrCv!=8%{hYkR5Sq>@e+hkd~HU6ok{YB8L!swQz|Q zjWqa`;$8xf>6@-!5vF_z^FW&}sS~;T3@nK~wFM<{q3<7QIV1?SyG_%^U4b58bp8Qj zv(l1IoB*GKjiR3nQBz}R&7KeoBcO~NLM(kH$hr3cffpWRXWdA489rU!IN$g)zi|g> z^YNFnu)mGCKjlWp9ck;YA23*m_B7iy>6?M~vtcE?QQ#M0%ncjoRWT4t%Dhp@a|^-L9jgmOx))Sc>?%7^iu}0PMmxLLoIAnL`^_OK{RwLVNP@{G@;+ZANHj{l3+Np4dw>y86UTu~H7;%iF-w0Pa#62S z;bff>h-EOo+_aRN*~yT_2(6A>(m|?9;)USXKe@`bZE@+%0(apAfMuz>s?O3p-Ulc9=jv42ci36L-L&e4LE>XLXPeS06KK|F(yS z5|{qn#qc1KKWv5D6C}vHm+6}1NVic%evlyX(k7tx?p2TW&97F94E63S1_>5Y^`e7* z&7s-gv1QJ;$5~JV(26>6!Db$}c)!Hc35Y-rp(ULD^i=6o`bB7M6>WHxu)`wJtrspUTG1z;s_haPpqC{YjfD4n! zuLWin(S`H;>gGqY=V9u}S_Kh@DlK2KG)>N>PqtmJS|6>C0wIUtYjbDJj>pgyC9C|fTWdBq{^E#pw zxrc#`lHa@fFZJcC_8HFj%>*64N1rQy2m)Ri;0zUMrjukFi3;aexfXe29S9MI=gXV>%X9oPljU7`nHt^W0HdcHi|SCKzuB;GzNv#|Mfgc zNG#!(d1xlV?yeSUZO4|;&S%+7;Hu&;+NR6(D^`sR+W68T>LK;7nuAScwh6xK?R17y2Y&ZQ_V25J#v0?T)%j~0tw|r#~g`}IFri?-*a{31vNoA@# zk(Q#})y|=8iVRc>j5r%nl;Dk*Rr5w5oK-28)(iQJW@w(+E^ly^XqYbpZV-z9ee&K* zTY@UM^w!tQ5W!Ew0W+_GG>SbkC+X zxpWX{{0c}cZ2iBnC*SA-xC;}?cm{o%+i1fZ%jE?>2+l=?L zh;ofI5j7Vm=PP3a%s|29wklM$q@-v!I`GQdu#XYD1z9m)*OYu&R51yYtYu`9xEcrP zAl|yj3k`7(^=(RbKOB=cTSf$gufE~1=8@N0ELdl>uUL^O(zf+rX6Q&dwhGG&8&#)Y zp5s2DY6vHHjG>G|j{3`w=roZwI7sPzV#VSn&qWZR$$_~_@eh7|KNyQZ;%iA;a=?BdeIepH0muY(>J* z!lyJqwm-vm7EWJPfRp&QXj|fA9H=8dW#e8-Qm5xoB;*PNvz{@6lC1aEUD33nO zs#!%%T*f?jN}>zUL9>y^y~@7r3(bdo+GR)}WrAS`9R>scJ%x#H#TdyF;fo&%J4c0%R)F&B?$_6^{_1E4KOx|WOX`2f zqzQmjl>wvk5nMA+A${AuE5!7y@&TBd*ECN240#>GDdSeYrL09c%_9I^q*IXm{bN)M zb$_T``A^z@`~MKMZeVdwmft*0Ngo1$GGd|irXT={_c%beaKk|^4~z~Ob&vDpw zNEQT0cHw(6{{iR{6i<@kNMBDbh+M24tRW;jq6J}D!*_5A)eu%INc+9>%^S0H=)k-F zSoXy6l_|7Sj;>hLuK3_7ecyY?>;?5LYEU$1MmW%8GEXZX(XHK)lL3(8DG;am9ZOibc$-s`0+GGyr991X+}o zh$tb2l9wOa3Cs}S4-eaHQ>^+IU*c4Nj?~Id*oH^LnIf_Da$d#+6B3&SrkyI|`x5y@ zQjoaP-AKfD-|<{SvNwqghM-i=KLm6>}$2AKvLE^@3VdLiy6@;??W8+ci;j4yX z{QubaYH~kHVK^uZ8oD2vYz&6*h%!7)y}R0#fN&f-i%e~L_o{j0W$7Pf8klwMIkVV* zA&}ApnaO;IM2>UB!jLv)CwqyHVNh+3A&bu9jS;ER2DZ<2EoC|(A*rcFgO{MHwZ}IsNXl|C*fFaSyRQn<-${cWJHf3%Dw!dP zRnjaAFaB5a$vhYQZ^kedM#Fzn28hLa++0!6F#l@+a$xUOiB`D(?*BA8V8@Q)2~D5L1_ZELgS6^%7mR%HIJ=GEV+ zyp9YCGcq8@mignTWhNZr8u`&?K zn27VzBLfNhxn(bz>^=Z<3B|qlq-mEgf2k;=2J1I=)%SrdSYKGwkt3KL?kw1qKC_a6AzG7=JS^RtZxHLp(V-vPxg{VR9 zW%1oe0>H3>DBn}$UjBTC?`?np|L^Aiu(pcqlei7 zzQ$QsPYOGO_HplU-?E;n zd9BG20KX}p`ggo>5fA(ZmK(Z~LL!}ou;9vF7Z|_-X45{?{gVizQD#!@xJOsQF<`4l zVn0IB!P#hFdL$2?F|FG~wo<^viNV7RqTQha{}>d*j0A%F;cogeBoSLS?d2kh#6l#m zo8|6x{X<@tslvk!-h5{NVi)BO*hHC=vv_VD5?HNW#kB;(9YY0fAkUY#T{R>bNx}to z3wrpSd-2m{R{s13)e>NX9&CBsTc7ZmK7k;`9YlKRS_&MKN%iAlg{7ASeGkSsU>$X*G=OFSo{lB?Xhj2eEOm=VQC&Whjp!N_A9qB*n{) zz(T1CKm0SyNsB6|m;!zhharIlE$IKH?@`Bq0(@S~LNOQN*x+EeU}WYW>_z}dAsh`H zD^`3v@(4Ou=jA5iLa_J)xutSR>`eS+H90SG(${W26#Fl$QMnGrQE;3Pn-E0NmYy{g zeFUk6S*V)k&bYzuXY&NkFnPIka=AT=DS{CMV~mfkBo=@QHOi;rrUzKD$awb~uDM4< zZnqLF>!(E@GggI>6mN{Z@;}5IMPP)qTI0p1qc808{~(~Wn*-M=-F|!Pk;VDJ#iWR_ zrnZ0^G&P`z<*SsKH#L?T!RpJ_Rk1{~@HX-d#hk`GF$WMo_SAl9URc`y^l70$fdiSv z0tyMFSaCPX0c=w4+kzED{ISlxglF-hh;aPJ|43wd4^@%g3cN~^DWonmoDDF5T9uY< z%s=^f#i0Kvt|T%cD+oh9%Kx7mWmvee4W0fU?;Q(440Kl9ie`lW2DBQAQI@gTR=vE6 zL~)gtbrew)FT}%1F>;dHTY;p-Byk>PKRws^tMW(!e$I1j>Hix#rkTLCii%f&L#_uhNqE4O)sGS@NpH98;m^xlg~h z98N4uGuO@fW?Z^uFD(vp170A%7g}#bD?%2sMkH1@uffwaCfCd{M)h{Abi$;jcC8mZN9Lwj0+gD{WbhqO=9 zPF$E9gx~)2gRmTcSv<*fcMT=HeVjYko$!Xh2KassmvbAlnjD;%8k5~yI2K01d67lC z{nCT3rRq(P$!FBp*OxAKh1IQ?&{qk@xjI&uL>U0%NKv4b!c$lgD$1S=s*W^rm3;2WFNW@$S8WafEf$wXsMN z)?tG%`)3`W&vGL4Jp${e7b$$a7$bzWH*kQiSmSq+GD99e`RW~64tkCnW%%WeO|a+k6=QB- z%7J`!j$7^^JIY8?!7LcWLR^d&Elq{FXC(O1%hzymE7*+fWsm+r7InJ-HZhl**JJZx zaW2xV#0}`|fei^>Tu=PIFzR#|Kp8y174XN1AbxKV&E0ngc6{J(f&u}Tw_#`=3y-}k z#`;gpU^uejogB-=cQ$|rfA>ASg!xKhU}%vk)28Kn!SW-b%yAl1S^-UE6D%b8kDHm9 zJ)~+-;>d)NpIdr)5mw^>z$taQ`%%ttf-HLOMErQ{ix)4(pLQ;tz_1zKdLLuUxnkF= zuvyd~LLPN~C0|Dm81XHZCvXhr51=a8?2;*&HskN@C(bWh9Cl>=#CX-nc*!QJ6&Ryb z+X5(yGV;Voz!-J46uAVPQgZ`Y7R2xcVK$P@u6e$KdhDn^!O6BRKMkOsF~XQA<(4cu z>NIegi*za{Sv%~heM>%Z`n^C<<_BJWqck}NPjGsrDXw7sB?F;VW7Y%Cb?#a6kR3&v z-x*j=9L)=MU>*?VK-ql~Ml+aE+qmN86(oeCg%x=z3sYcwK&T2Ae@q}?0ecvxS-0C} zHTDPqn?7yOW6yFKvl50I09HaAKK|sbygBhRV>A9y3HJ(!$&STrfwor; zLimp?1H5yw_hZ;v;7tCjrR?}^V)bA--D!y)JC(CxtoB=bmh;{}#_m=EfLz*7DmTdnU@VAFY)9U#o_lCp#Lyk!hjzCj#>;_2knBTIb zIS~Xulx;JTTuRU@xC9AydVqQqjw%a-^MIEanBEh?ZwkyMtlKZJ4Mv#V=}i>{MhBO4UJ4x^W;mp0*Is&QL)Cz|j5DD8 zAIMw2vbBIT9_92$!QwDzMP|hv0%5!cPD(lR1t^lHFoLo#l{$cq(~Npp#bds?X6Iv?IeseseDQdQIjr!G7cD2^-&SkKj{5A zFsrL4G!L35exsbL+hz}lVX>1Y0!^hVS_MC97W|k-h z2^_SVtc4D^`)u_<;EJ(sU;r@1wiu2)7_)ECD~zxXaip0X+#f-4`%>Vacna&M4lz1O z8hqiyMH)~Xt4nv2w}NJ`-*dg3h#@d~*DO3nB(+2&^D|$L62C8cLijiU@_@JLjv8cw zo5ltE6PBUu4%2Tuv2Vu;5qu#IemN?xczpSa!Cg2Ho7)?iMT6Y|Our$hVk2QHY!MRm zMYT}Jy+9CnDVhmSHeCKjFe8?XX%1{BuPQol8)E>A%V+l{zPuN}Y|fl{oA`ZU)L9U_ z#gQx446-r=mew*5qQCVh?3l?#V>bbp|7jlnCuTUHf%t9cr!0+g;(&+J!*q`b^O+#8 zAjBp8(sC02ADkraG=S#A+~lzgfJ0Q?(J=xcbR$zaJ5ikp`wBdahibo0Yier#I{IcQ z$rYpkwckc$H4H2%*rzGXJ!17VA4uMdfp zzF!a7h(xsr%5gw%s`78PCau1$3 zzU_R-0_y)Y5E>E7h)g${HWl(a$W}Ne@Ps(pw%srqxBMTPk|!`){@>@@{sG2i6JR(& zqHcS7VG~&;GR3`LXcIBw4%2K<=q4k(4Mq}=NgIRW5@KF94j$r605>xXI~P?35@?Cj zj07?20sB<#{`n#dgG9{_k*eif^N-OykbG^ZdLg+UC|&!zpELn~Ik244^STVOxXUos zD=Ni*aL4kMmVo)M^iwAu#|pI49T%iP5CdrdG5iN#`(|K1Hv$Qic(pqZrkn2D*hp6~}Ax39^O>GM^Tcm{X zhmvl|NtOd?k)t6gI|<9c-2|Adr9VopvJ-&dcN_|k=gCExw5^Lk5zl2y51KQGxpr%@ zcFdk-b+B?{b@`<{?84zaAk9>~iCv-p@!*5S?6wGi8+Zk5>RvJ(JTQ4O|KuCF(`?%# zxqP;d9NJ36{x%)~xNYY9cbaXNqn_tb3M&B7obl@+%IFPefST?Z?&E?I4TvxaOK_ck zd{gXh)}Z+0HIB(cX_pL?cvT6jUO@y*S~%m1?@XfC6g-0ti5FD`UKdOe`ps5Flmb6M z=02~kZYK{0eOZ4>-i{FntBQG`))46a${!)Mzt~Di?$}>` zv1psb-5_k^#K|IKlU5m)A1u>F(h-(!jGJt_BGC_T@KURhLF>e33So9@98a z{sRGAt~#%hUhK84!9Zt5mkCGj_l`A{dS#y$O(=ZEaOP)X>Ve7rS^hvlKE~Z{hZg^; zWY`^L&2?0QjlIF`(32LbfNs0B_DaA0{e zo1ef0pk0OLNynUC?A17Cz|q_Vz|QH_md>?EcK!g7^PH|`aABGsG)%R%wEnq0h20E) zfW4|DOl|lG{BsO8xYJ(0QX8;Yst`LsFhPhi21`o(c z8vrvE3Cos3Zo(2l;%iWQ1`8kBlNh*_Zfo_$Z)uG?@#HRu2x(|tt7{Ud6`S_6g#N?I*4*#X?Ct^GH%Ien355r2 z-ZeM;tSurC$Oux8v0Lqr;NF1HaHB0IKmyB%#3p5{Ao?zVE0`T^Q_A1n`#s-%&Dq{v zqz$Yln?#m{+;(*mnfPwUs&JdNmK7ejJUcQ?AH9WuC?3F%;<!*# zO?Hqnpd(o5&&~4(j7-cnYUUCw2JT>hSgk}-2S0$ySAasv6?|f-(bn2y8zZ2|hFBE?5sc_~g7)Knk7HJ)%xZNZ?+E_Aunn zZ;&-?>q;)3-Pm@==E0>!lvt7jaIIoVkRrkL6Sb#-u#oB5mr~iIJcPI}lO8DTE1a4% z7myK}+nq?@8v3)air+q~pwknO%A3Z-dZ*y$6Oj);HAOdcDP_-7Djv6 zHfFlbWHpa{=ugydxJUxvvJaU0wWbEH$34TOqx<@`2n=x|q5!UjicC)za$!m5BqN6t z^p6twf#!lgY&7Ka-jf8HP+pgH?Lx6r>L^9>hapZmp%h(WxB^e}_+{hYzOz%{;_yVE z|1DdIBJXWwmPrIEBu0>T5JSoYb|HCHIBD42pmlY9ye_-z?oF3l9>w}KtPIK%);O{y z@Jd-ip!ICpSx#4iT$BZo%yZ<)$3S&}UxIxVA*70}06}Y#f18@4h8_Vnh0(Mw&+0Xc zWQ0q)z$kA3NqsE+K(B7txI4D+r9-`kPX4QTs=@34uv53#%bG0>&2qTG(kJ_$dr%4= z0h*xoC=BhIc*{9dwPt@&tqa8iDjd|ptppYPN0|eAKxj{4C|{>0&f^h5kk02eqBKF8 z=H;ad;os!wg(<|XQidrvfDdaHc;)f~{p#cg(#cjkNg9ZVJ|h3pA2V##j{Okz?GLp| zq9B3p-?CQ2d74OmW{cVrdM4f0_c1ewidh(~*IAYG z7&U!dk@F(Vv@<42j};7(NWyn&lnc7Chwnf-ydPv4=0(u*+9*KdNHG|Jv4t6fyMlYQ zeYQ(&3Hj-8$a}j)Xs_VMMQULwj@4+G4mvnjHrqguNe(DNq$~H)p9_dZ%1(7J z9&?x&w4IOpLUz|6XXx>nUel-2mqZGawl;o?VZTj+%JL6K=>i?z4l}*E-cxhgh7hmy zB}OUNBaw|W<$Hu*)(IqFUj02MN?wONn8!*cX4^x4x$g5GXo(2h*v7M%&lZzYjWRYB z?DK*0(^lU@_YlIkbtprZAKpxhMPB7)Dq<=H%Lwd{!Su<7To6t2@OZRI2!+Vcdx<`I zkNw(-KGQrcY13%Eak^x2YW9`P`j`q2+0uy3i3TLY(2l+btAH<3LA6I#q_l5>nR<;o!s*EI-;;DT*YZzg(St+_iuBL&z0eC?3`C)hh z)8S6Za8)?+^gf&+Gzu0zPWLkycBu}d>DFH-?*H6D1kwB^nShSXly{O`J5a+>nBJGQ z2Y&sveP(Q8wrAX`9#0wtC@AXAl5-Q23pCQY+e#nLv$)^z|J!clt6*QaeOT#aOAr11 zNiQyK5+&KEAOT3uDG{TEw1KW2X@S>&Bz&b$P?;Y}8O3mK9@NVTLdglWTnmEDTY|P7 z{^~g+Je6_wbNkfUd!S%1&Aihfsz#WaqaN|g<%r6KS(%g#KZTkf=ufL(_qvFXDC5Y5 z$65x%!g>ur8q7&+?7-vNR|Vz{5Gl%n_4%&Gc2W3zxFZ=}QJ`e`aDkby8DI-WC321( zCD~gzYcfs=N#ief_ur%Cl-wrz9GXuN8nF$?^p0T=If6R{lEU)?aTMG^SU{q67B$Q} z%zef1PgV2Z?=Kwq6%`lBPGN0$r?eo37|jOL*%S+gBdu|-lKG!~{Dio)FtQMs<9l}1 zFv04J#DZ@VW)U!f?>qc+<`bE-N`_7A`;DrD6{U#4M^i0%X8s3Y{yoWBG^(B1+>4Al z5g4j9R8JPi1`8il&fY^{4i0dWhM3T13$Cmc>HOo&+LCAWGCOxsZU9RLOi2H7Qh~G& z?ppc9AoC3@h|Ckvv8N2mB>$ZnX4->&>DfywlchZ!H{0el0AXeCxj_I@j; zg!BE{_lU3rFgv4%UPQz%Fqg8t;Skrp=fV~Q!=~gVP)`xdg{d55X4^S25DdvEfH7BP zd#Wf z1t`tPvJ$CK2V`IU&I{#;Le0pgL@j(QeUeMH)yozZjKQfRlJLvgM1U-8YmBPm6&Qvg zGI=g6DT4U_6ZCp;0Olw#;I?-c2)@^if*O!QcP{sALR@sOd1kn%d8W*fei1#afcCST=*iipF>_{NR`*Z9tAlC zO#eo~sp(UK=>{wWbL=7t7FCw2leg{wG;FfFMD#7UtbWVt0~YEda)qa%R<3_Sq+{XY zr7dtN@5$=a?c_ktdO*(G$w?T24|gEwD8w(aXv-x51#vuG#vPevW*4hP2nO>8aPEa( zKIEg!fGlFH{%|8Xn7z|Igq!FLHcs zM*Ka^%P(h2QACXPq^8i~EiRT1%I=ur?`Kkf$E?5jvTipX(3r>UUNk3A#P)}dU;M_zH-kfB)&pAcV9K4!ej@-cj?98E|0by;w#KUw9QX0 znX&pHss`E8WC+kEITsC338X4|E{SW2WJ7{$0R%9|Hnb<23d@Co8Ub} zp+XP+nG_Z>LUyh$dn@#ekA5H}=a$hNrWJ^(u90>MFL*~mfHYWLSVvX~wubrR1P8n% zT21B-dV2#|w=hw69SqoqugzKk)%oRO-U5zDC2lr^94;>xU|ZZgN|3On0f4MYvYGuU zf~c?Rk-%pGiE0A>90|@4IRsRwwCT)mf@Csazo5m*RZ0`~hFu5Wg;&+mliNVj#lOBt zB#!@W=(@wfcSz{R9A4Y_t799B|C^uHH+j;H&J6c17%w+4`2dvRkEcK4DdT@p(uP46 zfZb><;wM>hkD?txyI4 zt^yu1E%1=~tpVD89|u7XKBNpHylsE&Zx+8t!@d(HRusU9vP~^~uvHE`8 zZ6~ndB5{CS2h1W&Q?IswL{jA%kq>ab2nH6p&kzhqQ3gR&r`k8xB85kk?|Ild|F!B4 zc!*RGOB~?_h8^)rjqE|f3Sc%;%xoU635dxPjmLC%!7w}RbQ1}b5fAUFeB2ZQf(K)Y zP+HGRv`+vqz6$WPT2^7qA|OGaBKAwv9z3yFf-7n6wBF(d1_gZLg_Vf&2P3-HGe`%> zlhoS6AQ`Bl;$W4=f}L4|JY@T81%Kz)g&P8hc!?h5#2$xr3oFz~5x|Gtfy6Xz#W(c* z3KY{8yOU@Rb|4L}jegG|7>2NM46!NvBLsMWTve^ee<87i#aEMwqnz?N)knv0hsofy#Xccb zs80C5B`$Cg1!jl-1Rdfu0K@Gst0Z?8NPyI-WJj{(%Pm{>TRCuzu^pmk-?ff13h>5tFfoM|uj_HdC^S_Asy)k9 zr%k4>n2#rYmLtpE%;vG*dAw23`*~MUu~p)$9E(pxdd2w_{B74YHXd%8(H1zb!LKmu zyg(ewDh-rD2M)2)oJfWZIXW4e;n$3*0*u*o7=ELTLw@F_*JPh~=R;GegJe_Qtlk@Q z?Tb5(QW#Cg)gG{_>kbrgb#5EawCv2E7}n2zGH9%qR-D{1J(Onsk5V9VN_r$zUgl|v zBYoCSa(6CrUdpdENR_p-U)MX zy`@$;ZDR9~ar$~U)wDdGkd_GnnqC=dO_ zr)yRxI|5YM8-Fm>(!O!2moq*xY#(gzvN*D`#5!79(zO9BG@(G>Kjf_t|EfRa#77Xn z3yGRFNVDDOQUl)Z*)h>*d%D>DQj@T0n#tq)#zjR_r^OHMld;pT2o-pG?oV)BCfSq^ z>;3B4!A>>(k*&%3hm^uZ@_tO3MgX)>F#}QNERaw)s%MnRQ^OnW-$6h3J^ro8;R0mb z*MFalplsJdkbvhzz3t`UO@>>{G3L|^scos4?Psp z9NRW3#7EXPKKNjNyPea=!P#kwr27o%rs~q219XsFViY0flokO84#)zP5&MJ}FYDT2 zCRAy>PDmwEFL8=Wv&Sp#(*1nR3nzPE;NQz$%GTX8MHq_~%>4s;f8`l2v zsSOKbsM@CRqM){`xJ&c$j7Df&+S}^(-(La`S{pq|uiVa9VVDkirnjZoV7;j5APtQ2^nMLNhz*&cDwkxZc;U%9JY}9j{O$L1$uv{MwW)? zA7?B}*AeI~JJ0vE%PdUQ)SBPHzbv2gZdacBU8C{0YlfzKyoM@@Hd+fXvwXKL3Y{FP z-Zxc%c_Q{i5R3TqN7GsF>}vz}T<&fzoH!Hd@B6XgJ z?rNnDlP#B0HBQ-5_wq4;Sf}W`QeE}g?k^O^?~~ZtkGkYGx8aAqAF4j9ByQ*# z>CqNGuf%`)h(zq}9bpgI*z!)FAHEjpF24H~MW+@^r{h?u2m98rv|X-0rXTQz3!T7e zz=m|9^$1bC4ZBiY#~Ygir|sMrPX#eN@RQk&SDf=aFpT_*WY>7SKSXH8szX(@9UaZg z1PGBbvp>LKe!eDoRdyC%V7+2nOD%JG%#)61>D(X;W1YloJ1Z%BSfw0=4~@6CeJx3i zJkK6`DuVgV)sLCyyVgPe6{HHc4lxrLPbKy0XY_TWE~wFO>Wj;Y`q`SD z;NI4jTv2;=F`7s@`=U=&{zhX_@}o*l!?T>lKE>hg6vn@vx~FnYO4^KQojUa+P{8!m zsZ+CieArl8+^(q{jk|NOG?k9aNV6`xox*KIRFEpKl1cPX%;`WHV>#~xF|!x$;z*mc zm$HwuO3?q>U0o1pVi28wS^>sjW(ngwuJ*}{Z(YdbK(^5wAvZq=hMak9e&&Lr$$$#l zSi43Ie1%9N^3oq=*imkPP>9)g)1P?nfSghoa+I+@i*;G!bI`p@H@LE&hyEg;_Vf*kAl0M6~FBcSJ|TeR;#LeyUH3 z(3TTv@w3Cy#ju_}!pkWsVWg5a&i8nqPkL|V_A)i~Sqaq;Y0-NT30+($Awr$9GcN`w_<-umGvvuSQ7qt6X>RaWpD@v96Nz_n%f?-wO zrKS(r5l*j+O5a@hIGSqmcf_Oj6@`q`RAQ=(!+ND()uXXtEnFtHovlr$lWJvr>!-r& zepR2%=Iv=@+U3AU^<{e0w)#VxWY%azOib6AFwU;B^W|N47|cGrJp4|soz8VX6{E{| zbZ_ovr~KkwSfBl!6A%5Z%=(yFdwg=wI*DI)OLg`V>aFr|bL^wwt<<<9{l9Wmzu|sd8R#$^Zo1C4aj=znj>}

*0ZyCPUBLj~<=9+>|cU?ez*Dc}mjxeyc+~WK93@)n*4CugmLYT(B;*J<2?tm-}XW$k+gX?lFwW zx%)!FYRBLEfA%F@Ex%!0?4Rxyb}N%z;fStFr={>`OKGl?Ry8>nCp{*#hI^verp5}N z{Cbtwt2gdbr+Xvc@lSE?=DQ>Mjt{C0q^H!TemM#*tTF9>a=#B%sq3A; z=A-E{4Kq3Tj6pYo)#-M%^$Rfm0Yv)IG8AtwY9lA(V(&mMCHzMYrXVKk=^><7(fA zzBzUC^yW8{j)g88&i@Ww`#|Z=eb?+Gitjh}uu9nT&;E(rTzpBlgSkv&-D~@*Zl0k( z+h0n=Y#2@$kBE9y?K+pqHt#uLpMiF!Pwe_ZNLEnNENRItNe427CYCH>H!dOSX0oV} zHbWgJ{x&q*c(ng?Rzi|Eo!Krc8*Q4{N!h}YO}dEu!F zGDY6345~#wKJvyz;Zk|MpRvuhpAA{1TE`O)J^DG*Ainj(qmxh6B|Zw-_dlpElupj8 zv;8y^X?sj=x=L~T$#9=DhzavKsRY}#ca#Xb4QEVb_Fs;ZFyM3-?{t|goao@&uc7~9 z>R746^!Hl%6XtU_r1uTx)v?^TLsud0_nN2I$FJ9}y=t(%je9Empt()xAt$9%Y@&yX zJXx84{cNWT86S{;r*i%EsoZ85rE?9S?-l3T zL69bQx?UQPbdIx_v*kMNReg5+ZQ9W6nn&-bdiP50MjiyKSe@twRqClwaVuAT`FEmS zaz6J5r#g`*RM^&*9TqxPF6tt@XLr|(Ofl52yE%1Ar@Cs6VvhF>@;?cOPOV)}J*!;L zJ*x}3e75t|L3Z774XKCIVJBvvk1)5?ItYdy$Sw;pR~QdC!fuc^Ri%(!EF(7e%-<>Q zdfJ`0$)9HO{8#(UK5;eqCT{K0WIl7($(x0tq9w!Wn)6_IZv5YXcS@T3etDcct>0Q7 z8!4|9GZGVV*DrhD3~+qc_aS5HscqkjuiO;xbo|+O{mNjlN4=8#O|iM(?@s#7Sym-n z&ljGk%l-9X&iQLYgS@>)hS2p=tvvV*yPD9F7dcpi$-6FvG5(w$M>teT2r@w~=@vSlBJY_g-m_^}%0?!lFD3DrzT9{ps z5GXC`Iy=ngWT5ls*EPR_YE52pSA|iRp(39T4XHAYYm}p@0C|&t*Nhjkv}LR5eC=kr z;5zi9pW>5T-=MhLP=!C&QEdjUzJp>7hE;sJ9bPi>9f4{*Dw$V{1}0ig?i-|9KdKWm z(EIb~-LG$C#!hUZH8s<86<&SI@PdO^Z}*DzvFVhDvWMqtbj`xDRQl<2gVz${gv)q zRj{fJ3ZK4Q6bs_@eX)0;rbegsLEP~Eq?aO(H~$`c_;cGODz1ufVR6?l@t2I>#nrZF zf1K8LWI5TVC|GJ>v=R$wZ8gMd)t9$ocSYP>U7=iob4Ty|NT8_*Nuj`bmzv#SH{MWf<`EXf@{vBIiT{M&YrqRr2X7imb6+Cvc*Ye%uES!?1 zJhN>&Zcem3*v}$x;-lMO9lINs!x&%XwIQC9{;OJU&saUb{%OGM&DUq^=9L~~iy)iT zdi^{xnomiO%j(``9lTHf0~#m+|_Wva(bczKs=Qk?!wFN*fr|WG*@oYabD=oc-Avf z2rbCGely$_r(k6`Ir2zTQ!iaui+ap?$otnWD=Qv1w{o8_+rxUPSG7Mny$Iu8E1NaY zu4@06%P)I_zD?Ql^Idrq+h{8$>xKAV zo}AeDvw{JJRqKCQcv~G(VgJlGIM`(M@_^}vt`&Y zGN*dJxYBj3oKYAbL8nI1mCrqVq&_z|?-T+r7qg8J^hp6KA(TZd&1wu0ZpLVo}+Rl`tQe^;%i!>zI_V4F9c=-|Gj#@>~Egi55}0cT~rh3)f&$7{V31T zTlK^sVS+JOW%b-d-R2K1w!KYi<(MzG>R3z=dm0Tjm>;bCIQQ&msjf@@Z*Xcq*K*FB zE;RfSl*YolVY+SV*ZpjJix*e+d`)jk^vD7KLP~R6LZi=@flKv2+lQRSos%sRk&&J4 zFZ$b)ZPzXpD}mmc+Q=c6`+pu9%axB^`T4GJ$j2{x%f{Y6VNcH9`rn%ECS#B z;diX2l|z2zF0Kt~0V=$Gu^j!DE_v{uOWR`aM&DcGF`Ik&?WAKqT(K#s{e@ZmzK8f# ziYI$*^_vac)jQtP#d(Jk*TmD9RD|R*YWqeuy_a2=RKAAG{Mxj%o7<)WV+`~aCe02N z`o5_PBj1_#N^$z|p71wRJSQo#lNqxiCU z#jML?-6mjBEGd*Xa}>``eST0rPS_B5kkW3WZuLcc^$PoW;jbo_9=yHsP#<5fce!c7 zY$b^yD8;J&vCv$`Y;mSx!Hz&-Nf-@#vLC<0=Bw@I2%ha+@w8_kdt`a!Ykut5Pds1! z*=_P#Rmp%+XZEMy+*-E@odfs1E18SR9DQV`gnj5k^V{574b*&Q3w{}P4$Cv}U*j`t zT z{cB+#btk-sjJVp7x4kfkxM_B0c@=+f9M2@Em&Ib>{i+YCvA1tJiy$dySi!5&1=+rD z8RmVZtGVLzN!NdtG%0Hv7qj_N@XJ}(RW@8_NL5sG*fU$xUwk3EFek?_LaARx2BSe^CTdN<{JiAB4dLB;M)r|pg#5cpQR`L2FLVV*@AfvrCTE`)sOV)3n_$fYHO z?7;gut#8>tC?s3eI`0}QJ;((f7kC8uLBtQFQ`9Ol8#$k>P$Zz!rq@qU-YHK!V6m>w z(KBckQ}LBS&B~}YgjMghw9HYO&1yDEZz1XU<9eyvL@wj0vlYuuvrCk|C4Jh`)@6J4 zxAu)O+f995H9skKc5Fc%vW9qa^qe{`#Y=>SJP}U4Mw-U@(YZZ!b<1IvmYWTnoU8Mm z21m0RDN|v6o$#i_0oqu6lTyx~fVZppKOS3G~(jdOgPr=M_%N(bA2#%jR;si+f#Ghvpho92LVjTpoadOP+j2|4Q9LK8F(zwRdXYjt+;3*UtKwpqBWX zz7vaU_`PH-6@Cod?Jhssh$3G&s5!*$n7;0NvN$-}cE&3~PB7(XU<0+uc4uUPV#3ZS zWthu775i%c?J~jZsT{!zhgSMenap;`WslGVeEBXbYWg9fVP?qZzWVA+ik7tZ1lif* zokpoQJ}Z>EZ5jP8MBrodm;}jg-)GIhvQPuTuK)-=E8FP;+@$Oa24z zridSlW3#+}=Yjl&{6JrqQBCcMykt!M`u|SlmlE~&FpYX;vxctt ziPQ&%^Xy?-v5fWJLZ3{lYx718MsUEDENQa?iSr>^_0WqKQ0(>rTRf+%Bw}L{j``{R*->loSchtV(};7DJ{VKPo_ zLhJPIcMb^)%A0ee?%b_LeklcF$Z=U#7H-ka762oK!Pt&&CgnnKhUL0npLcWM6Z7=> zvyzPQi^5@_L|wI1PBI4bnweh>=VGy=`<|@8X*Kb&|7CN3lH$)NB7M#*+XD%e8w^CT zZ=H7@M$(c}&U|TG>3h+?y=_C+w`~!mc7krSKFK!K!E}1rXifxQ#yO+Gr03VquTGjP zv0D8u3!52c{gcBchHF2@bt=FsdNseOOMtf|tj=NitNd(H#z0GPD9$_Tmb0$!%4-XM zp9PRe6D)0ip^>3~J;lqxU_@iQ_n!L3SiGjC&Zuh*>trXT={*{(pv6Kmtc1cHf>I)MNWoE<#`Bda z&*ITZ&2i%T`+|A#urkninE)Geu(=X8e<*Tw?!(GqSdEO)?@_CpHYU@Z(n-#>App&{^6>HUR5BvAZV*}j zUD~xvG;U$wEgQ=GRE_-gZZ|=uoX*?wl0v0t)ueFXFcyY6gV34pRt`i-QX%`Mfk#OB zx!%y2R4OtVJdhyeP6*Lrcbd^J2}^pD_Dm|m%DA~bEn5KCCMiIp-i8*(qQZxST)l8@ zur-@yXioJIeuYV&nR5BYc5zLbZ?kqos%`4Z40Wa8TA0sqG zq-({f)ANCeri`;jhu-TzfKFyXpY}C{OpW?7lfttuYm0+~Tsw7@&R2!mFTZBG%Q!5b zx~6gU?e(ky3VZyb3>_)Q;TdiR{H4updBEs)gf}UEyF?-;^IRMUrWOV#v}*G44c1d> zf?4h|j&r0IGiggDGJKZ<0$6>1YEHa=JBjn&jh{LUxjgkDIqynC+VBoz6G8tHk=TGs zIVwIAj+Dj7i{HWP&}$$B43}z*cd-Z=_!4&yYk(uWlup#1ebtg-Sy4JBw*96dxW9Bl zDMzO9F_)B44*~cqC}nWAOAncNojpY@!^6>1Lc@{jw=>sqfzV{54m4?VrC*wD>due$ znKfs_p2CbBQ%tTvBTK^si!%?``ark~X+HkcEK48Oe)g+)-x#yujc*M*JSy@t$mG(` zp!wCB;)3}u1@?y{8a4Q~MG+d3dIJrPqb3928!u=XIL?PH3?{}39%T%?bAJ>lHr&;C z*RuV40luSCfGD3~%R^u8hdGn6-aDL&yvIfcguB&O@Bd2GeJ}m)N=cYazn4S5E$xU@ zMBo#%GZ1Hf@lay)o8>-MU3sCSBL#U@2Agw&D{?Ixi-GNzZX{O};Em6;tH0=uaEUX7 zfN1!f1LfMSI+KxHhq@%Q?xPKF>R28EiY>+CSU#(s@1(rhxPN8wEFF0mpN?9Oc_*RP zpgUg6soE=bTWj1!ryPz`Tvi^TRCTlq<1Q}BX#!ZbR(J7BW|F#VLS~~uo}`~&8c)7q zLkbxL*kZG@{+=*a%6XxJv?ugQm^v6Q!<^n9OlI13ZFtytoOc4I& z$y!BDEo}*M?oV1)nGR9W2P~|~d5DYcDvby`cYX>5RXjaPB#SnDXQ78>?n_sDwK2C= zm-lLHQW_m;8vEU@S)u?}@mP+A&3QJCuLB9q_QqRDI+0$UeYS<8pEoLh2OL%&)b#_h zbl&M}{p8&4o4&;7kE5O{wT}@v8gImL%WY%8XIi;B+qAD#dO(Lh@3Ub+;C{$!Zpl9L zFJX>+egY@JaPj)C#fGLO(1^aYnJsCf0D^dLx;)lRNF@)7F;_oc6V~cGvm1aRrR=rS z*JLs^B%c>`g{sKaD--kERB2h+jM_4n%{uP*b}aYxrRS!qWS{4=TF%uP+>f`J?>Xbw zo>Qc3UDc`@Urr+zVPRg7IYQ*Wr=ic;c1aT}diB)J)7|DeBu$!`E z{ucA6M{wv1jr*O9;Zm?2PRF$#*|pZ1P!dD7ZwIP^OGkAR!F+^F!3&WTcARRC@jQ%8 zh|z2ZMKr5}nXG>D<=9DY28mE6a#sF>kXsK@I&Gw^F2qyNaq}8&K3N^`G``NY-PM*d zM@f;FGxhf9nQ#(*r=J^u`_Fy=%o|ii>PV6L3)*ThoiB2PY^bKOpWOXRT{UJKFB*aZ znT%V;RwwEx8AxaOf7QnM%*FFNe%>)n&p0th%F#odL7hFDq6qnOMofQCcXxZ=p<0%_ z)gG*UbssrbsY6gn5~pj3!r9B*>4fx_kezFObQCE$Qt(kPwQi0-QJ?ICE||rn0J$@7YuTnW#0U*g-mVI_B0Z6+qitv zN$Y7Z7mHcdUQef@wDIe_d`0t9d=IlIHrp#Hp6hU=GURXW7tbG%)`=vIl$x-c{a(U1 zO5;OLq^@LTVd+iWs8i794!|^4pfPP{I%lxs`Jk3~1hbR&W@%viF*_56!VO8sg;$;f zwokdlU$l_aUQykk46swP*O4CAiIf_z6IEaR;rXy@bku|t@g1eq|GOff8sNzNKB{hP8c!K$^s@)w zD&zir3_A}ig)@@Rf`vRZ+h$7f)%(hcspQFUb7h9hAfvS8a)h!(b z4)UF=4C)Id3OrW&owh0aeQ-w#!U}$lNICx!XuZU5SHEV~+B6JdW69TjITjE|{ko!V zmmrq2y}cX-hv3^d4bRM$b30h?Y~d6-hcg=qZ!QW2eXTuw%HF4Kdtq#DaITa)H0F_M zXM*_PYrSUuJVnNMYHA{k4ftQrDuG$wha#i8lE z*4_<;&qk8&s)otJ%~S> z&+RKa-rMmiW?<*~?BmajcJ7kbBJaCSfB`U&dxvzeYRROssQBo8RmbY4)v5)CWG!&o zCp@nb!T|So=Rj2AJz(7}&_W2m1rYxGA|ZC{LDU}L*k*ae%<*5!qHqbuP>ybLtkYLwT%KAk0J)=S@l!Q0e{Rp(ydAh7$Kr!-a)& zU2kk^O3xL}UOOPf=QNxJS=B>*Wo7GI70c6eOOSgo-bdP?ayChlJ0s2@Nwwtzv}c>X zrCwZYzZ#pl4T(RRgT=(rgRk)LLH*=0qt_%MMek&pVuc2~6=?Ubdq+{&i%i zj@!hrn*{QVuZ8Ll7d)M_2vU(_SJMOi;qL{F`w&7v$?hYc-|iN_BV5r1xZ?ky5(&}V z=FU{_p@No2MLTnpo@%F0zs!5=dioQUT+546dM~`I?MD<8Cy!_f-HY`M{=++~T!t3fT zSD;PE`$uL@jP)gCvfRi7T1?c@SiL2?^~n|j#&xGHB2bjUXs7m+tj#`GSL#0^x`JH^qHND4|QC0@~J~L ze64?ttn`1&jNabV7hyNNC{Jb)7Bh2rgi~|iBKBx{9@N44@P%-ye~!&3u!R%x-PF$? z#P*_aHGr?FRiF2`g+c3&sEZ^fL@aVRj>$#!IRF|J@CZBM@D)MfL{);EBo@Yu-M z7T;Tr$`7U)?NxQsVba3Yw>Pv8iMI>-y-e4t{zk)p?bp@dn4D0R)3tV^8zx#NjXQR| z^u`X}nFf|GV~qdrP7HTsQ(3p6uaMbl?tBQRi?3nlr^1WbnX~dKckf9Wv?@tn|B!Hk zLexR?jY)E6f#N}_b;iL~qD-#5vF$s|5rac_(%iZ18z|+|{M(Dyf(!=Bd`*zLvtE%+k6C5abD9X*G{D(PX zl#T7-4)0CNnoUc#5tU2-P9vlho_u10tcjtsh zs4b?b=L)7zK@*UJD9zx^>gQbk>aTVdMzS}n310_NRFW|mO>mgDhj{Avh4IBIa7N2z zISr-qM^FzRIl@yAP?$uXZ&UL+y823neClg0QjQLlDV=Sbw09D2tbN6A$du9|!oGig zE<>-rU2L-cF;>=sPnl8u$jX^Bld4I>o5Sw|VhaulFSMrw=oAH?&p)~nywaG+Mnq

uETA!nTGhKq+c&m3_a<0z4)!=si`Q|}~j2G|Z%FTykZ}@DzS4>edw=5IB zuM_E{(x-S)y`gB*&HRQ2bn;52I>456J0zO_fGjPJw#qRaXm^8)T5U2WpL_`YbKf03 zZQOb8>Ew%j*F(Lw4Z8^!e=_qS{&NdH0v&V4OIeIC-)^J+Ig9hmxi8_v;kA9tj=TLM zaMR#nzf#FBi0pypQCA=Xn0sql=)r3r<(z6?-u&46_ddRpYfUWjP!ID}seFIVH(HbZ ze(dF``}s<2@j>?Hec7t{kNWc1(w%G0blzw6?YEhFZIo{<790I(O8V3N#z?Q|Koup5 zyOqR|G#pkPRWqeGJAZA=MjOa0;K-yJx=pF)ddZ~xnD(2uA?(#ry%!ik?y!7&EnUk; zC+7Zrfr_6mUxdefijMRu<)?G*zPMx3E!UZ8lz-w$SFu=NbgoR`{*`WZlIa}vPm{@q zELA4G+C<6(TfGtq$1^6al()J~=9@j1=DU}T<~QoYDHItmx%uc$03d)aXA z(zG+s^iX?b{soMj`{4;)NBL#OPwh2w^nux;)c6km?v3*R^#L4deK>{QvJJx-YKY**40A~Sol1t%{k!?LgrCF$ zX+_JU_0Y>={%EQw zU+r!aZ#xjt0UY+m*cL^uCLp~XV`3lTh@rNBEl(`}sDMW_V6Jj=q@w8z(8K2$sXYs5 z&I4Co(!Z8YlDn3mn$Nyt_1Tv1OfAXprTF&ZO9ZdOBWPe8z2nWFJFU33^QfVLGyp#X zJ5>QSG`XkYq1di{KayY%)a}N}-0p)Qxk5-ugxwn$MJJGdRi7^%^uluEL_D!D!D0go zP8G3Fi?ros`k}VV>}&4Fuis9TNw`17dFZdUDc_!csqi7y8m2hi!iAJNf%TZufyNuUw)ELPKIAgbiwbs?&uwNa8?s z%ZIDO%B`R3itfjXHD&5acQaZ4z7Lk-ThNz*@)ug&*+$vjvu_KmVTkG{CcmH(oYixo?(NN~_ZuJ@2) zcgzL0V|7q04c8W03sxQ#S{L~J6x6mh-~dSw1{6Tq2gN@00JqP$uM<>dt2vU4LX9E! zD%Q9+FZWM#EYt8Q)t&rlVs0#*4&8@o&}ZcmVf*al0`LeR z)tPxLfazOxZ;)h{qXfR}2M(d2YB~TH5(vU)CKby#M6+LI-u0ZbihXB)$znN>PW}z` zowvaohJU@E;G&yW&6}!`y3o3Fgxkf_=??Y!7LmK1Ma_ZAY1#*R@UgSs0fDe;QcF(o z4Yi@mHdn#!3B2Yt8t9e1xnywRvjpJfV0xZr)TCFF`mS~{?fs(Rz1fN!`YpE;Ws|i3 zY%OMzH%@fqStY^g>R;KzR2SN+`+SuD><;*{pvLb~ofDQ=zM*FEJ&G1II6XjXW^@&6 zzuDPU1TN%x{GJq1JZf;Hnf*h82s0RgPT>vF&h^&1PU&lv?=|*gr-H8gvJd(Y`cHS~ zgkI>$nCIwRSV$Y;BmHk%`MX$2v{Bq@1|5XY;gPUDi^7pci-MgEZorrM;ToZxPM2uH z)E!l829=HPphqwDu0cH`K>e&QJv_q0z#lexT!c6$TWYBkf(1{ z%KkUcz1!pd-A`d$3qWzVLA2C4vH!*KxY1vroBE@%QGF<@{1IWcVU;S06o$> z_d^}966Yl;kaMot2tDqj3qXyeY^i@Zmj`%maPN6lM9xgmocmOnkPd46D{$-pZKi=? zs1@}6Hrn``+X!Ubb~cK4GVqK>y<@YTc7@QlKv*~xQ431!&X^-nZqwFtcyA7@dykjd zt}cLnWUoN_`B9A@M5~ROVFzF1Y)1X@G@&OQP2ACjOc1}=9mIu}jnBWFXGVR7so=~= zi}UF(C3cDL1w7^G!BAWr3D~?ZXP*4r~}}-0klVa26YnXg$%Hfwh5x@n0sy@mCzFSKn42*G|)>(iHSa2!3@s)65J`@ z)PsGJTMsgv!r5$Yeo29K$k7b@t^09pp$%E(h1+?E3^bw3ERFWjdP{i3zkwlU)gUR$ z;{3;F5*Y4y2K)8w>cwX44t;?(ot2MEsQ2wfomD>`c)+9Q=(; z1-@K>3n^|i;h}YBLF=w;8utPtt>JOM)fVO{CxFCHxd}Zw0^bB~!K&YSbS~r|+y~2d z2JSp_jQs>IY9Q-+4YzEUG2B)W?KLQ|pw{nS#NjG6{U(eG5x5H@!LiaJ-c=~*p>Xn; zh1kgfCq(;hDrnUMSy!-6qO#e?rVrRbt7c@z1K0tF41k51MEP^f>){_)#2^Jr?>P{O z(!=M0^rqTVh&gg#NVaMG8Gle;De47Vn^f6>c1jms^SsTT8HsDyMNrnq8}{xiK@`%H z_2AL&;|a`Q=qJ=Q;*gqIMY~R;kca6;iHo|r3S2wn{znuLm?DLI;7mLQD6IlJR?4eg z$G)A40Y)}oT7Tz+DSF6>Q#4q~0!!Ej6PoLLO@Ri*iB_$QmPG_JsK;D9`+rY!&%Os} zXl;QHd*Dw3i)b56?u4AcZUc^m?Lh+ER2b2ZRJ;qjaK!By!eFcWSa{)SUua+=d?iiS z&{(XZ%or1IB>u&mxDuSk6SHl1oOr(ByV8xLB@pwZ};nt6C;wzgkj!h zUdIs47!2pF5JE^i|iN@r$cn)UK+`yf1>cg-G)Fq2V-S+7=$k79ow5`s6Yb>v>z=L;A8;P~j7+VPXzMUA#bDjmpy6 zyL?kZ7O@xSF|yu18y()>(1;2o~eA5anv8{;zF_?dVBf|Frwd5aVT>u?+2Me$8A`xkZ)T*bfwlC(1 z7zzGIV3r~*mrPgr87YM^q^bJeGGD+-3JhNpsKAIyR7r(@H{m`3Pd+twK-(Uk<|+jf zaeN$$X({X`0Jk4*F=r&k-i{_q1i!0jn9-uixTc|thhr_UKhFaup<$TnIlUK|Gh|F= zC;!a25Jb=HopH3*&ybglGnv`q7GhA01pl)#1adI62dZ2Z2*ur!0_Ij=VS@YK~_j26K`w#K_R?j`>Svd;)`?|UJQk#UX) zTGBCuV_gt0iQvt44@To~InhI&JrCI3**$w_ff#H}#Qb2w0%RbDzCH*HL1M=h<^-hh z`?~*#`2UFb|F@9YhkG#|udgYyGpvln;ZnU+1b)$`?88VUBu$_nQ}7_^HFCrtuaL)b zd|1mIk^R0M0CiO6onQctxM4mo2dyaktNQ5UL3lY-cc=!J8h_GQ4!B4eOXth#ggV zQabB8`US?Rr&`gQR+8I%6QOi^r1q{s+K+;_7TlS*=>VOm(vCkw&}jf!#%hywh8S!# z0Nw~1ipkomWhj0SHd*lj^Jn0Ozg<$_5R&70XCaO-kXsmHA}+fw((Q(wO+SfH_G{A{ zokjPGot+>=?YB75Do!i&;iVA=^1AOOBxcd}4s+RF%WlM@;8B>#o22jlWM~5&)@h)I zJ6m;obD^O|R$f($`^|KHIg$F+oz+VI+8X$c9!?^B1>uIbl^wt2f8fI*JXh6=dJ3u( zb;2*r7Vi}0HXH=+Fleix)(3{b%K`-mvKbVHV7(VS=-b2a5G_sw@hy6Z*>*ANzubj( z02BibSsFh_$P}8(K&b|el!G7~*!!H6-Ka~Z&u)~QYwBa!Nl#mF3*h1!cBTtWv?N?m z&M{`wX^s*+w6@qRRkvlxQM9w|pw}FC=Ti+s-q`sVeaTN8>74HZo#awg^wc^(Jrr3R z3Xp=$*hJvIn91fBU|wIB>_tqx?uR6N;by4E`rN6Gf#@myDCW(S3LggY=4*F@Iym#l z+MX5vvIlL$T?CMa)0uQJz%&RhRe8K-o7Z1LMxM9*;;X#6ldL5FmL;Xm&32fYfm?tS zpJJRuh-P25QkH(Yaqsm6nFN2ktD!zXJ_<7h3-y{o>_BSHYr{dBmAw?JK$fva-PzeS z9>>m}ciLFaB7az^4hyus-jrVqZ`)joMRB`5dq?3*n6eo)c6}cp2ke_b%`h|M=&qF2 zsoxd01h{aso5I0^C~4sd%vf^ljrU#*D1U^g@F;5}z3B0gG5pwmhgse$(sAB{-1@DD zQ%sp0mJLIg9(r6=&CBe_T4y*FeB|lFSv+BNFobB&tU&ZHJ6{zP~ zkmzR0;U*c_7>n_{ueMx<&;L4aU;BQ19;*EZk9}O)8T8{9Lmv0a$LSe{oy@sFl#eQX z$tzy^at`30<#V*X>k+iX3CC>RseHN@8DOqL8n~EQcz`7U=a*u%!XFP+G;$?hH>i@p z{25s15iH2fY>i_mu7iubxvp1n$}nczt__%aUF!IVt||3eWt{Q#o+79Gdsdpj@975Z z^xd&_p&Ue8a}I0s!$y^zH}<%h9?`umEO@@nx(tuw+qYL2Qan|Rh|Zj;-g}6Qij>{( zwq5V{D8XT0?wQ+b5zX!pB?^Mh>2Kp9h;>=(-FKdI4Wxd&%fXA|&6Vs&@Wy36+%2)b zYRC-~maXaCPWy7RIMBLYLEkA9lcum2#~gX8by|9`E~|g?g1%(I+}j_i`~icuFzrfG z5qsp?96&c2)73kI^l-C9gjt{GKwc|aJN1I0=q0^s-sugqE$Hkp6c=hN7FIzA1fcTc z%YOXV>dtHBVM0lADUa&IxJHV)eL`4t#PY4Do7CD8q-jDJAKdI(5x0K`5!d?=neK!D zoSpv4Oz&`@yc*Mrd-e@R8eO_lTY{IG=d{7OMu^#@HI&H0U^j!Y%3WPu5Upm!>fnfh zDIcHtSmlp{{t=RS;+*tIENUXw8o}Swj9jsr$iC$~IxWvAqO%Ho>m}FlF&je9HU5{x zlRZ0x_qY)pT^wr^6!xPq3M~P*^R1+)N6iP1lU-ivb1>-=U-CQ*!mBE8zAb;Yh;B5( zAvMK?+$G1j*Rb+&rXVBD?W7U~g4LWogci0ZF+}qiz3?(!2`83>ej#~a^JejhYdZ|S z;7lw(mgE3)MZqq3-VMW8^csNM9}a3@0ufZvaOLBSkIn}zEZ^)BK^OJrQoFi1EFLCM z36xZ@++|RH^ID9skN;#^F<;VQtErBAd>d0N3teT$xh1q4B8+AxD`+Ae&)ci9Pj?xl z1?oiDRTN}8uFa=gPb(#W9btD>mlWQVect=fUfSQ@LmUxcXVbRFudpWg4~SEdrCl69 z^tVZkvHkz$9Hv4+iIcckH1-OKi!iMv?qC+Pfm>WlEz(A(oPU)Zid8Rm#zdk8I1hkC zzGyYk@+zF$=IyDmuB;?oEs`9g_EgpT`PN2_E^%*k8~8SokDX<{b%sGLjPoL7N_ie5 zzuwYglhIgUXQBAnG|H}usgmr1y3ZriR?gV*btqf4i|U)BKnJz#s0q==Kro2@(ZKSE49a?9K%Y`Vz!#C$-~vY*2Ml zT9G~`sQg6!$LgS85JT~1Q<$ssNPDv41#mu(Fwu|(&`7k{!H>;(GI1nT`G2!1I6 zRkcSi{t1S2!jcaXcr{~)&JgIwkPI7xyoIt7OwY8aK#0|TdVBRE3)9HJ_$Wue|yWEpG@K5jhb*VTpG=0V! z1b^kIve3 zPXGG{Wgfyp1bD2Odd(HYA|cFLCxf(>*5MZ~kERgCSm0C2X(X;Z!a-$Eq6RL8tt`eSIvIuGhN5ZP4Y5?&na&$ z)x%89e*DsfC(=2_s-5{ZX={D_+jIDhI*oUlm)dnMH>Rgdv1nDF9^{tbw^`RI_adyY zadqB)%Wg!wzGl!E{q%wP54S_q#-poNwZTlnk z-SNPD=#_|b22W0dvjbQM_zw*C;5eh)rRnhTtAhmH5`HC~e zE5BecG&bHeya1hj0n{|RtPK-cP>)8s6csGBDUTF2h3&f$!ZzRzekD{Pf+*r`R=&A$MMWBc2&_tUX66w zw$tR8o&@>gP2j~AHV2ZZMtyspXpjU?rx)U26&bNR64bKIKRo4bn{^d!%y2)fW4n4+ zK|x_a%ag+X1JgmLne@YZV)NV%eqUdiWtc5k@QoQY-q~8=s4$&O$?H0zAos+k-mK;sb+Zpzj-PS3$p7Fz{6M@GhM9y@2J7(~?H64XNqkAG`iYFyIHiH9EO#`7aM&I@!}n}4p-vG7e?TaJ7(rddBL^QNsEgdwAKttu!O+z%=Afis;0}(F zRe^+?2T7K%?p*SLmAy@2H*})&;{$ht11Vl+$dJ31({n#L&nuullh((BEX;$&f1~9~ zY2ivAUAv0Koq1*7mDYRvk97Lppu^?e*<6S&Ih~WTE3X&QQ&sb;0e=|GTB*-MN?yOwdK*=P)ZIP1vsEId+n3FG`}_7Dz}2;FZt=?-h@ukUxBj(;C_j&Z4t z_EHR4LZKI}F%>?dDx>n;A=|!g^VLmI?nCacEcZ6~rGMK?Y`o?Z9FA zNM5VywvdcI5kKhDfQ6? zviI{>2VM*VmTRzm>v8(J_0-sBTCu!(TKUwgE;}3jLbK!9dIa$nqxJKhI+3}BnH_5+ z+Q_Q$B41f|bxuvoo1j(BQF<`aB+igUE}j$fGv{ZSXQ+umc|xwm=-Uk?rrUIP6}y{a z#bfWq2)7C9<*xZOI{@M(ka-q(5m2SsYX96>&GMN`bzH9)1zCI?WTD&~5w_xl8o8 z&f;*8y5y9$W2nMc6pSxkx8i%=WtoFnL34WfP%4nefbl+D} zDk(!TGW29zZFel-Txl@@dZeQXenBpqug+^rtS?VM1A%4jE5x|_%Hh-L3qLVt3i{`t zd_G|J7Wna&kx&ReqB;Rupuq&0d5hWx4PUvM?^Nzt;ijdBnVr@xC!;dEO$z2b=E_#U zrT2CeT+iR?IQp-Ge)AhEekaksSUIN@CA|MsTU^QMtvMIKYssF{#s-0}fZn>6`}E?& zsTm3v{ZHVe7rx5O02kkdmr2;1HQklPQYs=sX7(gmy;zO) zSlo<~-TCH%Eaq2ow?AcrUaofCyMG8IJE7BdQ|{x(S~$&QX+FzJaFcx;du{ZqaaX%c z6e(WzMS0Te2v45NvS{Fyukbz_klp{46wD(6)ym#{8=B))z?Tf5rTr>IX~l2W(7b_+@o_cmH;3Sl9cKbKlzU7OS`B+!x@^)s{PE zvnVaatA%5cT$$3nvpLG|^RD+Tn?bUtPeYhT2y4q&=7FCUL1eX!ORdGDtc!1SN$Q(5 zg0@u?`>(7!ZG(wJ62*e|!Fsaoni*{xA4yNB51FTLlOj$zw#} zSFjqyRe%u@)kAV(OqMe{5X%E)rV5N{_|gY}q+!cuZbh1g@3w}l%jxh<_wlL#8gWSM zC%+?1ck$xRuccQa6t9#`3R@eO?EU4Yhj}V zY?@UtdfUyruxFarx5}#bqFSGYWl|loNFHqe;+~qdt-%}0{;%A~myhq{o6A9&sqn1-0 zpA>%3$hv@=#vhEmYRHJK&B+?@@yG}}=)gJ@P525q4wj=x!Alqj2)@JDt}w>!6Iec- zd94i}DLXa=8Tos&xKdwar97?dEzrzQPp%Ybax)TxHB6N z&hkdP|ExR)no-?70$UaPW$-@M+p-VkSG+q~_L1N+0KKw@e>`79$ZrwxBNsWE3C!W1 zbLs*f7_SmGmm@ALYzU4cX^7wz*75^ODlHu#Z#Nl8GMTAE6WOP(f}$|ox{;qX?&mM! zOQy#wGmz~o;(ihgw}fex6z!;zP)j2~xzYTkIoTkFYjJZG{J1*#0O1+Q@+xKOo&#+{ zH`g>Y$(sDDP1)qR++nlkOigbO*)#Mix@vK5p5-ryUL151(|>?FqC4!D)9>8f=-5=Z zva19^^IM(mZ7=F&b(&#Q&`2SRX*XykLRVBTx`vqvW@*-#vGT>pw|t*1fTXlbNf9~RPaZLZZ% zLy{n3MW%!dFIw;A_dBbi#r;bb`glyViOvlZD@LN%E+hOvmVy)4>Xv6Ye!oe^mfsTq zx%soY6bx`+Mz+|uvQR7ve}Yo|F9-SDsJsOqB+k6m!|t-Hz%Rs}CU=NYUgRIRhe;{B ztAr?kaB*gRNJA2YkvC;AcLMWE;_~W1y5Wu4;vitSdzsZ=H`f+UnS`V%9e4z1WJSx~ z3CpYx=O%5{J2|R9{3KR92svk8at^I70saoS;K$Qsy}j%GOefEnDVVDuHLto96zrKO zmqJ^-y>1{EcqGDcUNolZMl?%!4b*h8mM-Swq%iZ*(Oi zl*8lMo>^=ywRbw|Dg z26u{NhV^t;W<&l#8P?jEsEAuNGMC1{x6QuK^|^)B4gyqDXB=8Jq(j+_3@Z=ek;oVp z8qr*w)_s`@L9ghjJi{(`e7?&F$V}f)c^}h%K=N%ngcW&aug}Vqgn|3wh4biVqxEB! zW8X`xL*C+(aelYHYDaTBG9OslNqD3-Bw#5ch(~?75T%=hU*g4Nd%Z@1pz+Pm*h(`u zDik{yNMxkO*@luHPa2dEkBOXogT&Y{o)Qa`u0}NkTz|93^%y1kbvN6Ui>nHuv0ko) zS{5I%6UO_lK{W0Zc~Dm2=6p>w;fZu3KWLn&+v;|!+oX8Qo0b?xEJc=HsDD|oOrULf zCVy589LXu3j@^>mZq{4tOG5kkV)dNX7f)=txkT$}Xbx8RoTXbC%LQ1admy$oN&ZFX z5vk{E%IX1hZ^ss*++In@`XslV6K!V>jqBGU8Ovm+ajU0tn(;4Qv7*n#ZC0%7u!aCgWiFu(Mo6xL zC#zCj-5!IO1*j-tz2~Fqc~6)^oToD6lnypg4;U)~!K1L($5Grz0p+EaGqZ@IEn0?K z9xvdoo~Ugks1hPlz1Xg56V7VV*R%9Udh4UQ%N~bs=R7~p(3zD~Qo39PhrZ+#Nm_Hf z6bEf*$MJ0)ou8#o%PaamJ`jZSHT^Sn8_&01ilAtAxC+XBN}6tF%vQWP!fxbjE(A1; zR2?D9qdf6>hven1!{*Q|INy36Ldh5LJC};&tkz*`YbPytLQRU8Rx&zQ!Sy;68>Hs1 zUH4WLM6J&RTgYR+^^mnu{vEx(M72VDjJKOV6feb>#jt~M;XYozVOM5V&&oqVx(G7c zA8zS3vuYU|#hb$n^WZuQ-DvXx=aka+_DXT6r&xOX65rg{Q*{eg7o>lUzLu7J1>i1p z)PVRARP&8E)3bxNyRx3F)Sc15M+c6rObW65X)8t=+=hHfcCKIxsO^5Pq}A9 zte_m5DYJDDWFedg;9`UDSb|6WCms}lU)X2rfZ+{kuxCU|c1tZ@q3CN!?4$E1P&lPT zNJ|VW4XFr@gJ%1wKqHJI-8jf*#`xNiq+CT63HYs3>k>fOu`g@r^J~p?kP&mwj@qmQ zg-gI=go-PukLNnyIetp_5JbNNK_lj)?F-|wcn|eU&s7JP0Ko@AysyK$Hm@0?cNd;L zQB!(#}GM9J^e((`NzR=0?_ec${K=uVI8hg5_*q zulv_N4Ed7^q_|@6uW?xUtgJ(1tDd4#ZmWLF#Goo(94&7~mK%m}HtM|E?U(1yA z1nG<~zTFns28&B#YGg>(owV)lO^|1|+@)jn684dUs!bXj?P;(ci}LhZ5}71P%$QN0 z4JW%f3Bog_>E)72Z(D;1m`OsTuXM*#J0UWwOZcYWWrx=+Q8z0XJe93I4due$A#7yZ z*9u0eMVq2d73kK;CI2xU;iPky-flZB0t~+dY7x6_8QOIujnx{NKMr4XiJuPp#A@sQ z=oV&AKB36+3oUJb%r_?FLd-azlNW__UE;9j$KM-w2{!JY(w!0HOZ=x)2Y%fxDCS$M zuYxY%{O_cSMSfH(n=l|vHBjGdodtFou;3LC zixHxGsSxUM28CjHA9c4;_EK22+)IC0dA!@K7oy0%Cv+Pr*K!~WpkpJJ>)j5|jh*47 ztX}JGqwH#$wv{kr1^|X1`ZRf6AbM>MAVC?Qu=}_@p1C&-@xcqN_5WBY#@|Zc`zv`s1H7obn*pz5q|j#!rqMe z*B}evUaWAEq+&N@f?Qnd08clKYM$k%2suOLBz3(@uWmJe49Xg~H?Rsma`c{MP}Jg( z_mb=>mQ|?JK(V$q8tLO2y>vF+dL8KVg@#XD>DdpfxTWJHn4G^BgLWRu zsoer`Rt3o78j*^ro9@!nCiX&k0I-I=kQkwkiPJ$!1Tx^D#5L?PneV^C<(fXR zE_!s4avI^HcyY-gkX7O z{tzhk>P9cFObrANM)DYGk&5w!AyPu#FqQX# zPv~=pPR2xwn^RFYFOh_C0_7RS>t22*70H+V(Ab9cY80Vp^7Kfk$(eBZxHC%{z8>Eo z#IAMWS7~=!lzTW-h}V=pJxU&?m?xd-RWC{tE|VuorsA|d9$$kJ<02AT4KvqKB)0b9 zdf}%>RVOWWz=#eMp|Jbf(~ud}8kwndCmK}R9Z2s8?_i_tx+ou!H|Wd#XzY8`2$v-L zqT+Xhf+Is0XF{-4YK4oTQf|l@lM>-nJ3R8_VL0(6Bs%_8u0+5Ag2YVOY=o2=K0Hvm zR)Hzv0uB5MiTs4&N6bj5*MsaC@_tK-fmlOuNQ)%JT!8Vk5Ti)`RBwI`L_12S3Sqv& z-`)X69AS!wllx5Fc_6(^QmSFS02z7*j}iZvScfE#8c6b>M`4fJFfM68|FQ#15IFQ# zHqI4-gfGb}moHzQb4(eF3KUE>>iGCT_)F#gVDHW2YW}|eQ6-cNk+Dd|5Y0-OjxoZK zp{NX@G%2YRksL$D5{1%8lc)?$N)yqfIZ881Nh8gp``oW{PQ&N(xWC7}-_PfF?|t0I z`yWoP^E!L4z4qE`UTfQCHg*(->IA?q9re*HvETX(EJ)MTyEc!7GV^q=jolEk{n9Lf z?`ir4<0Z6tQkrGq^OXN~nZGZ=Q4#LF5AXB}jg=53zYjlANy?+OLZPdF_C!hT8$g z+B{oQkD7Lmui^S}X75d5Xq9a-3*CAiYd>#F%O9l zFAvoCS3VhMX*GB9>aU|bZk8IWMcoW9%UdqnGiUL`vD>rP)Q7we6L~J+3g+q)YV0U7 zH|Xagjh0Q#{a-Il7BDdBqOU)FJ@C=X_WGi&0c!WAUX@C45?G~X7WaEoDYXA3zl-bg z18&B_b6nFa;9PJ+iJquRVihr9a#{8Kf(QplJ9R=W_)@#m4bhc5X34HP_?Wd320 z%%g5^{%QjWdH(t=VM6ADKebqB`7{6 zT`+jLK6qi=J`I=Z1Q0Vx$$TksP4i>kQ~8+V0%bi=<+F5t4Uk;grmH9YAOlitXh#|l zh4tKt`-=%4lMoaB;`~GJ;y`H{!k;|R6FHugWT5sYn|N6bR>s1O& zkW~+?uO1g{l_YxjMDIQvUOqIiaiHyV(`+2z+NgkS8{ktcGfv$## zyI%EF+JGqcj3;2H*L%iGCraQ0PwOv9Yf9%wULn%)Jk`bLT_PshACi7|qOFXSr_zL( zKCuJuLgOaXz;m}5%wjPTJ*1j#5uDF}H|j`}Lh|3{xqt((f~s`F`{;fT!V$s6iB zI||Cp5OmR;l5f1Ot^_A@am-VC)aR=qo$Peb!u5a3EOXL{t6AZG96z{kcJz@CH=+z0 zV(Y`C3nhx*<@EE4$EjDGYu(;DLp%K_5`1dT&yxG}8_1Bo8pfCrTIT;KzPfg~M~fDu z!-mlY*8|_WBQz&y29C93j_kp$J!M)u3N6nPrF2oO5MGk#HWhCPVPTdGlvwQAJ$ryLS^|9 zhD+E&%wqj@gc9lG;9qw4pu9%aq&CKTW|GBO@k{b0rhg{iY(KQXSpTNLD&u`k56`r+ zO!Hzo$>7mL^2W=#ygkVN`4|ArEo%AFHOE4Q{qLGOPg{^TrhzotV8_0gp_R4ka?a}5D|NStbN3H zO^XkH@M5Y#fy}g&HvPGpN)epaAM(qf(6s&ehH-0oYq-9h+3Ue%$6E(_U2Z`5sY@K< zso}CbFl##53pVzt<3G4FGmnwIIH;7uEQ1`y`G}-&|N3#Xh3rKI*$YQ+K`M|^O~NXZ zUT%GT4~jaDQTCGzVtTh`_blv2l7oBiV$)3IOle0QH&WQ%tArGXW!O^CSBn8gTzxeyI&~?cx_lubJzZJy9;Wyy1UX@ z>%gt~2|+nm25##28!Wj#@|RTl+gyn0gpiY7hU>D&2f?F6U*3cZ zV?V;G`rb3I%==-|zSJufx>)9!1IVR*oD;5Dpe7UQ>&FlXeNq(|4_Bzghv|F2h~+I` z5AV_)2o3jC54jU!J@MZoJ|mqARHN@5b}tDIv!h7eaFaEC_|qmdQVD^tP3AeaE6O{L z-c~x6nckD2e8?<*YSB0xh?ndG;rX6lz-jN0r9ZOX{ynk#%QQW^9Y_SjP4nhUwoBX} zh~Ho=?@ns{ok^z$c|TU$tut^WxTyB|>mZ*uuL%hs$Dd90dpkMwVO8LF$FZ{`)@^*O zgtQQgtW`yetnKVSj~830>Lk-_iM+Av4*IW*h(W*Kx>!GIYb98MU#xSZuYN5Kfh$rC z+*L{bj#kuhcf$+wORawe2W7m!&Y$%$()OC>lx4w-_rA`13E#^Wpn7+Hz=w*WTH1Q? zLTroE*duM@dai3GoI8Vb^+W!2uQk`#haHMsb4T-PUZ4+0Xp_{ru9k>?!!KJ#ixx&f z$1{REwq5;a6e%yrfZ1v6)C)3@Sw2t8=L7iM$Le&KEK5&ZVRYHSFl23#=<~SzCy&FC zkR9$}@;qMC+xFKoOO*l#hdPA*7ZujGw?3Qid%V=|w(|1mGK-sye_%mGnUya`AS$Z8 z&WgBmoK~^5?A3M`FP#*6>!}+u+K+N(^38Wk-VHuPNWP6=9!(l{T9FS1E8SUnr`(rG z)0MOtx4w}*;vllq`HJo={A3g-vwK02MlFJ1yELx8hD6^e8nxOK$-05J=BZRD_CN7+ zdZm#hQEC42_xGFP8R!c;_1JLYKJAdGXTA z`IzeHEi(_Ee$-eF#<1%QT)^s0F||Ua`@EWtSHx+_bVVS^vh^`j}3N}e2e z6NO-O#kprlSA6_=*DF))mo+i9-u$7v{1{KqMLb%6Rjo3?PP#%nQLs&6fyezhbGCj? zC0W&Q@@;R|e0fwLEsyV!rvH|Rt>W=}4%)SUejip}gcF7Fb>*T~wZgt!QNxW;2qA z2=1FINh3nm85R&{%!&!)iO5YliV=h^-G8j0a#4PbXZ88WKi4$>nrnYsD|)jGQa8%o z=&S0bRmBnT_7`)D>s~1wu!Lt7(_SwIdhjJp{)<6!7EIU5=U)&eXUYOg%9BxqwW5(#|c7 zPoIU|#WuXPE`eKRXMxvvMFy_*;o-riUP7%@@SRmk1bU1WM(? zAyAD3C5hhri4%j6M*SY5Ni})l(Nw2ZlT8BVhPqa_Ssp#uoCPuV)|1>P^;^`=J@7~ zpOCI1n{_NgyK&RAihZT)Co3(osW=nVu3pjzedOlYg&}KsCepWgD{2YI9(U`A-E+e5 z#oE#H3)L3ET7MZg&{H2wmvd4Y=&H(pwpYBgx2LM>rdVv6b!K{YaFF+Mei6FNj+J}E zJg2-4)`<=~v*L3KG_YME>zo|)ykQsZ2?|mQtZK^hyV_6@U#eL9<>>8uBrP14=x?09 zn*+LKCD0}|Bu^vxx$WjDrs%uY6#MLp{92@y`pU>yc9z?erUWqp5lP5^FrOQGqaeo8 zptMyp-u4t^j6g*CCxj#fNsQ_EJx7uUdu&- zkaTI2KbnHOqi1hh}u%R5Q9^-CzDEwKbihZhn_?=iRNJoWW0JLn3;RH!nuP2rflu1lCIFO33iu zC>n2gi2l%FN1JMIVTQDAIZA6NFLmh$sB@0XXZHLp6?y0&id2<_a}}(&%~}3C5~T=I z2<4TYF;+%?bLq=H3IgG*bap1`Arp*qne^dgC5I3G_a+&435 zu87~X*%)2KqIO%Yl!-HcKSlM6Q&7nn6b0OhjIdpj&8vnW?zCT~6V2;pp}6h*YzbZ3 zk=L^it#Y-5-$HP@c8G|v#siW(X}h#BD(t4RL1AIQ)|tt%>#mG1Og6$@-%avT7z9_S zo!zDq-cuYYYgduj^GlSJd9<6CnA$nX7(P1uZco;B2?GR-cfoh8^O~M~5*frXJH1wD z{9Qbk_g$-ytFDtaGHO&Mg8YMI_cilp@)X)ikdA-C*YXYvikxl69MaQ<<5jXw*X+zZwUrO{TU<9?gd~m! z%62_poB~RV!wqgn952p)B6CDZb+i;LZ|SGc{hxWaLIT{>jL#cCzh|_SNj*wM;$S=$ zu3r`%8Jj~=tk3Rf4rO>F$wtGtFvRF=POM+&yw&Euef`b$(Lysq@B6)d>_T6YZmn3J z4Rc1AL~1nD^DN=cG+!uTku6{AA7=%H+~Ib1IBY=c?0J8?R4HMIB8cF1yKJ|kmE6Wl z>jchio-UBLe3|yCWA85W7@e2`WKC*8Y3P)Rd4{vnOCsJmYtGs-Gb2B#*sinO;Ie_t zhQp=;0`92sB%vm|A>D_>vvvDwYzz3Ho9#{?RUNkUcFl!e7Jbu7^j?!9S4! zHJjWqzmSRjJ?{{o4);HXRDAu zvNV)!&~8pKzFXyiE`6Zr!W7DM>eE4Ql^vYqC=Y8Mlt&a7uSmTm)MJLChP<0j+TQm{j13-bT7j=Ia%b?c~;b!$a9LU4Zko~kW#3g(#_(&(4;pbu=H zlil?&r&ek6+c_IEI%}PD=_#tK%uh_s-(ughun)z{f`^0PReu||$Ph)eaELOsp;R{nC*_vVJq<4# zzKS{D*Y$hThv4d`E6sZHYw5L*at_3f7dPt>&5G&itLxFxK?q9^A;bV74~Hxg(Kjhd z=|D8vQ+AtXL-n+A5o-!jiR=D-Ztc~Gb#9ux`QsG}YY-7CS~5;9q+cs~mH&fa?c0b% zW*4;zMS!(dw^}}kOBgrD`~p&QH(lMx>kGV$5*tr7BtAks^Mm~y7m!d#{>P8i>xhO_ z&70Ojr7Z&galrq(H`cevZlncafOEerPf#3|*-6hTz4JFINHB?bcr()YN|VW{+h}Th zcU+f`v}VXY%3XU7`3%cMzv=yY0#lcGE@x?|OL;x0i-%UZw<)35%uh-!u=Q?bf$jz* zQv4tZLhvMuBsLn`=lgFVbu80)2%X(CrTSvs+b2vvlxurj}+iMUVjGk*59*)T1 zY*Y>3nj?>U`C?);*Nd#FL=?Z|$@pq`+ShCMD3U6j*(&yCm*m$xKd0Edc0$dQjR}PD z?NL#5)TN&;ym5yZW;;<7z~4Uo*m;V-4c0slRzb7bo;=vz9i3fENZN5VVS9A97|PP4 zc@t0Q8IBdv%?Ls(LXWJOGI1Ie=Si3;D3I2L+MER>OcwhLA>DJRdeR!BF$1+|m&Q%L zZ$HrLbIiUj<#fZ#)C?&5^N~vXfMi{j8#&!3MZ$0aow$4{!Z;I^tGf=qfuXn+sVzh= z%YS}I1@%w*8Lh9BR-gH!xA3Q*icrzl-LZixwnZ5mkiC2#rmnxj%4b!`l5l}(xrt=$ z%7*wq@`u^0E%v5C%FlS$<0ChlOnyAxP>}09cAlCsg6&f$w*QQegld?#k2mp>h`>I` zv2l6`NWFqbFCzIk^4HoLig%kObRXNR;ht}8c%}-%TF(3IL<6JM%MTP7Z`MAbmMn}Y z@5$-sw-yEMO1;c8xYEj)3IIKxW>p zQP(?SP!hjzwd-lwh)NV8A1jEQlAHdyHEjCpkhd!)N*T=(E44=1@ePNUQyU+egh@V) zTHH~dP}5^6XZ2GZ!HehenXHJmaZ~t|71-{#+LJetUSRKuPtkL)PYmVN+CX~j?b&<( z%tq1*ahm2Gc}n=f6EM7_#F>;ioH@ifmY6ZZxFro98JbhqN;qtn@y--;?QX3Vd@fL! zYkxRcV;!86dY2Ekea?x3x3TJK2TZMggHf=vToDTstZHdZxSp`eHT?ANBc#aZu9ARK=Wte`SvC&qv-|JJkeKJu8cJoz6G^ zTw}O;$NCe5cNt35`@7JmhHARp4$P?qW7v4<_Cc=SFbZmuB|j^5mj&$lHjCY6i{T5k zk0(P-ViI9d99(hyxi`vC{@5P2-WJw)4m>5NH*Ha6ovjNO6o8(lxYbmfgs+ugb*!@L z!2l+U4WgkZ@vhgzN{JGobc-;>*T%P~0P?V=kKM%OwZpE7UHJ2oxG3v?82rJ6v_kyo zRbbURih1%OoXnGVM3ULkkOG!;iWD5DK*7fJot-md4m8wpZFoOm>cwKB=5BH{p;pVy zuBsmCyra}AlGT%SN?(&eG7F63^2yJ$q?sz0JO0$`_;!H0Ltt1daIPanDinUqsV3@m z2zd*A4XA17lQ`Mq4@aI*e^LLCY<1a5%zndvmg0xEyUJKy;0N0~o<>|ab@YZ<%9lm{ zc?BiU#ZjP$qU6D*o+JCek@p@GMgL)aMhH>uB}gp4>Z#;0<{l5W=BY+P6J5DIhP31( z)c>AX^&t;%mhqPsu?`+O@lqSQ4qZ}3Hr)K6&fZ=QuHjY8tl()3tMSKRf6x{*0dyL7 z&5HD)pk^4+uX(Uf-N@)Ifn)=LOs43b771D%CR>rBaWs=P257{k_^Qp65z-aX_8JM) z>=RdUAxh0F^46-kGl}HjzPuU3U7K<95%m`Z$rn)em(|Rc?`fn8D3 z>3SPJxp@I@qJ?MdQ*8y7mGl>et9Qeij|v?27}kb-o2Dhb}a8?iP5qXMq5fg z@?COIAV=TAeaq+S=W1eiV>0x@EZi2{3%=+vNBFJS_-m4!N6sl9Nf)la5YTkT)i6BT zzbU?`#3Z!2Mfs_P(Y!+K%3kF}yL+D|<0dKLhzm#PKf6}$xG;)Q!FfYIzOIbA7HK-q z(waTf?(ZDDId%ruG|L&Q<8Vj!D8?iOiy0>8hhbL_9-5Fa^*YC3{#i!toEV4oFEQhA%A`}dCs?%CMmKm z!+H^GRT0a8rDI`HxZ#%Oh__Y+9z!F1y!av!7#dJcSnRwTzX(bQXWfIEiojr@N)}8w{v94R>=G(`oo~iY<#eu9+3LB{R-bRMJ$Tw>X@VaQB2D{LWz}BwfWPex3=7Av|3DYuS=+4H%yl>(<)+{OXy%*Ao%Q;06z!vMY*WoOsoD zJ6OA{W#n`WWeh13EV)XW$=drNDmprUuVi@Q7xL|1Ou@>1d4_r>&)mcxV9B{UfKtv& zkZ@<;1sLbXJE`Rq#xPUTMQ~Wot8#>&JMRP=Uzp&>6r!6k-om%GD;OL=#aLP!H(4=} zh4V0kvT+k7^dL#8k*-00^Uv2;AaaQ;w*)X6N$=Hxmf*Vy?+fgfmZo0(atN&5eqQC& zG)NVf-Ic|xK@IUz?r$A${5T#fvMq?A#Qbt72*FEN__4?3UI78N!u&KPux8^$sqYHR zTYYaqx=2qANu4^2h8ad}xXprO#D)RAg5>!>=>2!}nrNPy!|nw@QT;!t{{I;zI|(U0 zl$Dd~OiYXB;?@HnOMlmd8w;4HS`F&o81gw+73|{n!?WbEuPlyb$y90aL3VKp59+55 z5Pg+V1`ZL!Z>~&zIqM*f-Ra#s7D>2*qp08Yrj95E{0SAeU`pl>GPXGwX6j@H!RrDX zo`pIg2Y(qq!`=DXTx($TDH!(+0D$ z>bs8%>uDzdRE3opQCJx_z^#^#|M-M8E(a$spyZuXL9s5vif(l|F}3Z5FjH=E)xYE# z3q5r9sJw+tNH{p{FhtLjn5zWLX_kFs$#Y@wN&02iM&`Y*+*j;}0E0vB@tsWM zZiA4L4u}e;C?iP*lvJ=<;mQaJh>?aFufo3A^rYNSkS^l_=Q!`v!lo5=hEd(fQg96$ zQ1R$vn~E?vUnMCt$GITyGOPHSLgH&@vHn)U@XmwG{n{c)S41z}3 zKZMwaiDoX)3eT+Adl24y>bmtuI|D;|n>fpTQWoub^ROt zx8B~mLfJ#CzuK$*L_bq^STbEb)7`c7Fq538g4njW&0XjF04o!hm4ig52&@ptQmZnT zAEWXSz2V3&YaA<@A|v$nob5W3E0uKmNQ+CM=x^S_7VCa`OZO{V~@4?a~ts%_i`v)GN(7FOXdhb%(~48(Yrmok z#+Iv-=N;_I6dXEVFC=(7ho}V&2qfB9@Nu9zR}r$E>%yUvB=nzN&dU z5UA?i?$gU$Ie|XOm9sY6Yb(ks-fdSs-_l=H&{TX=&aP=R1*Aj(hIK8;hIa!SS^%lE zt*T+#2pK>oA96(CT;GuASRXBg$uIIAS338@zQ1u{soXNfmZZ^j-Y7V#vz*7q8aJQY zy00jC+_Lv?bD!Km_gB>u0p&J1mM z_Yku0j_l0;F>^;{Tg+sNCD0~e^f|w>nf{(CQ2VX=`Z}&cCm5k~K-k1fOqu#~}m4sI9%}6Qr2dk~RU)1f)JbBm%+FnL$ zZO$RlZ=MP^Vh7?*OCGSZf2N|KVy|*wO?yXZ!k^g~o0~6f0W5B5l z(#FC~e#f}8hRcN^5tQhqK=_>&=+UCb8mho3hU4V9^k}8?^VsGJUf;;G|9tnh*uD(+ z$H$~5P;;;i0S?YaXaNxqf*nh+TqBXK9;}_aM|_ww+)iS6^=mi@^ViP0L}q@)bG+=7 zgsE4TdP&@$XcZQ6@P)>uPejeAg#U1w?3nUUyPMLB8t zmcsN6YP+1NN*%mdP>7YB||UO6#UKdh9iTU#co%$R%0li4C~XDnZdkMbhHrE~M0U&_QRWFV1?@&IK2||& z>O1*SL9{zi4hsK-+-D9`j3I`@*BJWGQ2CMq&FT`XPuKH7hBKR-9uCfA zj5^`Ouj^b6#5J#$Z%HixU(6-k_+pGF!D1K=N7wwp)nm0z7}3{#1p4^M9RDhB@NBp@g`z6%2zbH^tP`S$ykNKinUa># zr{?1SQ`LHE%-!`}i_IOoK5{DHTmr@3eN-{q+l?mz*i@s{`=7BTD)=)I^T{oD$-1~@ z7MP>GXhcj*ahhzQa4w-KoiimYntC#%sxvME?(yT3mof>8{B}8ZAy`6YA&Xq_#n1Tw zc68QC>K$Kmfc0*))WKnn4tz1p3-V+y=(?g|8=bjSf)Wz&n7T`1Gy#pmHsd$hTRCe; zM?7(Z31nb=vUbWooIQh6qI=n1lCjX>X1f+7<41f@9YwZ%nlZF95;w3{OGAVPEe@LR zYRvO}fg~R_>ORnef$6%*!V(YhqG5y((+uP08+6?enHmZ}%o@M%W~9{%E+9{B%RAiIL>5IOvzy=1aRU}F;S@}G_jCTx5bq$4 zE<09yAAB-Q<>KQ8hWQs9+o}oj+HGW>!nEtSevxTa)(c{E;j7rfNv90hn1SJ@`n6R2 zuY?k^;fvSpQ0_b3^X|9H2t`de*8x8glrLf00GT)unI@;V`>p_-ul`6-mKhKWY7kNX zq6x$%5shH<5!-1L{)Tt}i#hNa?!nIH>4w5q9JB+w-vJxrC#RieA>j>%Xgp>j4Se1r zMn_;6+i)CQIqL&M7U(NU%bP*{txL@AgUKeG`Z$xW_pTbT2!)3681JHppKmNEb2eXr zAM=}kgOTQkR~^TitR!2>4mHgb14d8NQkaSvR|yEpNKVHC$3}M>&>(qAi4~jrx79`v zs^8=k#r&cbW48(%ZCf6agT+$eMh?M?!VSnxevp z6m|04wuQM88RDeTeWD3Lk;DaL@Cz$2ssG={+(Ss?%!L%Dyod&ijTv*}Hup4WZM!$~ zrLYL%JIL|<$FdZ%=yg~$_r{!mBNf)zra)JO*YifQXkil5%`7uLK+f%4d3PpT<}-xF z5%g9)bOTnygM}}lS#u3;EyH1qSaVZgdAV1ygP@>?k;&1K7Cu)AU^H5&szc4kI0Fhe zGx0f(D;)8=HH89Ml8_SsbB!;3N&v=e2DELrhomN-`d1xsr3VSEzgokyeAb+oviqI9 zBa>~sQM*MG1lmli4VkV52_om7e_O?P1!yxel4Y2iLL;^#?Xb>0?Dc3+>V#``JDJ}Q z`r*VAt_kHCgHPsT7^g65VV>on`*IY+7EgYqXP|rz)Sye? z&Yt(w+uxN1~(_2EG`I_SAQLT}43H0y5Zm+w)39Sd! z1uqoY%@REmvAHs{p5DXz5)5F5E6s_G8MyFXU{jO7X*)7E6<;KI;+;7T4=6Z&r(pa| z7b7;gVCL;9#>*&7(|n0CEwD?91c@@RmCMBW`G*vnCkUbk1x92J+d9PS>GldX6hw?& z*fq87Df_v0?aKTu%7#S`q>#?lybm*sJm>@vXJTksj&foieey)&4#3=y>%=@n{8;0* zYmG}sD2wE+A(t>?AC~a)PZUCI)Y3q<#53wJqg|2xfP+&XgN>>!-RquzV$>a}+K2f^#-psv#tl3Fv*5(o zUN&~gzocf|05r>_W~|4i`<}*}-xo>=vj-l&#nAR)Ou)=nEAto z!+e}_KtS1s?@WIP5YF~<0?1H&6>CoAqTRQy^#8Vn zHi#bTgAB1^3||xye#~Gcmz?_s=&y{^V^NLo0W9A-Jd$`2Qi#5@yNH#IGKdfMhyMbj z9>NAW{5vuArc05OtCk&h<%MRkOi{RsbsQT(B@rH>S@@v@BwV&ztoIQ2VCxc0b@4wJ zR&2GCd88dNwTwVWB!QWouyqKAB>fx5?AuH{8xqT-{J7~RpjQ$^lk%w@X?Y_S$%gSC z)%fFoPc<$rEj2PRF&S9+oQH4e2JC^e@GYib=6;Wpdi_C+BTc(cA?bGO0m zDIq4U!l6=1K_<$%TTE#zap|qGkXp@>U*1$vMRG(hY_p3f`Y^Oi9qW^+bGb{qa zJOb-L`j>B&=Tp3ykbD(~&U0g6=4q8QNlpU_)x+^PtIpjEnCS*@{!^?l=oPv^Bca2U z{)Xd*4#P*nn+0Q-y74EZ`0ds?QsYRRP0LK*uq6F3d7=()3w_nj+3YkDm@s!R=EjsG z!wiZ`(0?b;e=(>NC;SG#=-{3}8zJ5B)hrxRgZ+ngK=#s}VT{lvCgMYI4f?IALC9&1 z0i+fpU(ZqRNW_7LS^~W{>n_FeA1zX%Vw+fhE(x_Rbo)r;*;m;6PV<NT61qU8=4m&M~!_YRKA#k~TCx0YHpl_<+3hwa#W znJIpYshN}5i9PM)3Pw3c$*t7|AhP*Xo zKQjo-qLT{%v+oSds(n{VWN1t&kV!`7hdo2cZ~%{;t^KK$S~=}Je$hAE?LyK)g2D7I z$|s7iBwpc^r6mbre;J!FhQP!^?V&N2OzrCUsIo05!P?lm#AULIcY=9e^?#ewvgI_B z9{>0HPFCBt8J3ildH&;__+NP^2FZ&0Fp?>lfyY_$PMbI7|3&FPM4+Hx^%!ud9~=VA zJohp3FgAiJbK{KpRa5o7w2Xlcf1 z8D_@O>nk{JE7i|!@a4nfbr&VZGI7mE!ZQbJWZ`5>;=>7DztooX4Vz0d*%?-DNKzvk z&zVv|E28=ysWGcx0}l2pE!~xg8QLWbG10+zF}Tq-Ql-H0YA{g<0fQChXKdseo0>9Z z%f5WsrXx6*!NG*(U-e~HfV=y4+!EUV9k3a%IV)}JV%og+`uK318|X@Jo2SzE{=dyV zgo&QW%wA!kau-mk*KnWsD;X(-rA_`~s{w(tXRgmTO16$-Jj7#TdQJn-l?K9a$*D*m zqA6|SdYivdfh8R9#Xh(X!^_g9Q}3|z%T6r94sJFoWeMA*sH+HivU&owon?-_c=DQ{{C37(VdZns6it{W)biR7aJ1E^m< zDd#63N`t6(nNpcI1e^e&1>p^%!)SrHV8$$ZKs{k(V5gV!X1;!e|JQvm)nbW%LK=7T zC3r!Rzo4ZAi-@`mMRS%C{q_BxOgM&_6id@3j*xHLm6*C{H3WCM*M(M|25e?{wBuk> zj2~#@R!03dfM=+QF^UaW!u(~AF%@Hwq^H3rgK9bTWGt-v*`A+9^GtvRAU!K!9Gi?{ zvCmem7OTiOeb1myANog~X5#dJi#nYYeQ4mYZAaw1tiaQMzPZ%#rtZl8Z#(7LN0_4H z|Ihh)axWy=9QvDHt|8p2(buC5v-_G== z>j-)ro%f@Ah>>uIz(x#T;#e8z0cp?yrRD+L=rufZQIbb<0PBi5S~&GMjCSBCl8a%WWay& zr%Fy{9Kz$il6P#kc9K74a!kgd{?ed%h! z->$L~S@zWwM`AJFF&J~&9H%_$hcx0u_ck|M_^&~UGRWXs&E?8es)atL{doD@kl`vE zm{d8?&|CNB`QOtKKU8{iQwtvKIAMgRROkyjU$Z)Q1pRG|KM~gN886hL;M1)Z+nm;M@K7(?@WPfe z-eGgb^S)nA4XQf|t0F1VNyM8^s#w^Z-5qo%HNTQjZ&E0JVixe7#N633DQtXSU1#E* z3i-3g%x@i4bqcKP&pa_{U_VQ>U=FT`!G9lDVmgYdNx)=r^M1||t|1tMLunQalBD$T z_Lqx8^Vsq#w*Crkn=Ib%+u47%yeoj;N6qK#PyVxZjh4f}8BZ*EiD?TdbTC$7m_jZq z1;!MDu=nlklbUoMi|noIav~V)06fK8Q)-9i;_mzYy}K>ZyT9ivUt(wViJ&)TDl%P` zN<)cxqZlNboD$xyN6qbwV73C*`8-%LI~Y1*O+_45 zaSlb~uNP18`RT5(;i|~jnCSEO6Mh#MjMEu-c3a7vE~Fs&rB(47!2okJ86zT!Zctoav3GLY4b*Ad|^13!HRqz;tClyz4K zo_K9qPwVmVv0r0qdc3UbUMFiD^7b?m7$3};P{P2rzA;ylHUMe-YZX{YM8f7M%*}RO zK=IF{<@oN|(irx4V8X`-`I5Hax8I_FcV!oa_1%;oU=t`7r|ts`+PS+kgGhs1b)@@} zn<$}u<8$ z=uD;hD9-Gyl?~loJ^0WTZOL|*=&rfir#fUt#!OZj|ZmeOUuk9k%%5|q>a1Gb}l$%iwOu207cXm2E5k6;m*xX3> z`SZ@czO5q4Udo-hshxclGY4q~PEsT_>^{F~33H(buuri!V!V^AFj;|?0tqgVmxmXy zgun<0U*P(27zQ^d_I>Vd6UjLjAO5~srMt8)t?}xb%G$9*#L1OGGF?P?T_|E?5Ocuc zZrhxp)I=8chNq8~!7{fTeJy$zv<;#ywJLq!NA7Z+@y7-}@|RBb4^rt4plollYU*9z zbwJJd_0lAp8{3N{mT<1LqLJn3!h9q}(t03@_}&lU{RE4qe_=)kw^er4NsEgA{haEp zCUVDL@t&cqNqTT%jvOWCgzyxA##Ur2h;4uL~t;YrH)N$=OL zp|sUe3=b}`#wLH2p47x`53I#1+sYm1l|`Kr?tfeBL-!CAlso@p_^u91Va9OdF`TGy z%w^gVEMQxU6FVVru)D(WvQ7$2KAo>8&j0bU`qlX0{sK=$>jif0FGU_4tR3bN=E@*( zPL+lHlo()yVU7bI_V3VLgE{I#yx3OR@Irr0Fe9xg8T!L^DwA-dEW(Y-8~P85dk<9G z(?z11OY2R1h6@g)8qhZ$p3U?T3{sXJtjhEYO`Dn9c_9fY9di6|%{`CHT^G+)h>Nf7 z989($JV*?i5c@}pnQ5_aBS>rd;v&8@^t^CMEe~enpIyQG6X{R|H?y|0p98f0Q@ScE zdym=n_7})$mD$u@&2K)}lUT;C8(?B=WZLSW_VEW|qB}N+I zQVlXd~#O+Th_oquAO5F0b)2FA=syb@@U^NJnPJY2V z6fCFU!ju&8U_c#rf6Fua0CKmq1PdTOWOa%dwvg2%J;XOAoUtT|$Xc*DBeCmSWzd7{ zILY0sPqw{nYfJwtbGDh8cSuU=Hqm%Q zhfe5!?o1VH`FsA)ww}CF@j#bUy8mUZMHKEY?Kw=rYgSKzOK*k?CiEV9|-15 z(7zn^SMX{@KfonC*aJl(A?e=V$N@))04sCRfYRu&_==XquPO;|Nc|d3*fXh)*WhoY zn`NbHLC4#L-#Vh1X3Q)dT^qP}2Y&5Rn<*aD_Uz-|o}LT?qKxUPfwGoGdNI>h1Hm5L zQ;L_3=vzbHV%s5rj05tL)4@z1ZGdE*%DiQpl`I+)O+I*zQqW7#Z_1sl%tX6#)7@@O<4Rayw7NiWjE>oWoBAxP=$_ zWMyUH4$DVYxcbBY+4EsVD-@Wkcp4fS{LzEWAH_#O&z^Vogtt~)v8s!a>*y&VX3{>e zSA&(xa?I1d0JGh=^+Ek#iX5a!R*lJYmK+mX1Yt)p=GF?>5H>QB6cl1#LJls^#xG|VlOr^ z>G%icbA53AI`@9e79;lU1Z&HZ-hi~H65nmeT@N(Ru! zaR-=f^N1Qaax%Ch7PX*fToBqnb_U+F&i35#;CxpN>$MS~e)aN6q803303;*VJ;v?E zFv&_ZnjtlUMsiB;jWayQ$ruwQ6fa_0ZeMgUsjyJfw)&20KCW|_soXha;V>atZr;&Q z+=k|kL0(>7SEh^iH&~UN>5spXLz+YlX8cj_x{^AWV`6~|%y9rxo2f$%f^$ZCqX>W8 zQ5=WCs1?MF<5AUo)7x`(z6>NWM%VpLoi|u($f8*|vNwMH6ui5RTo|;k^U}OTFYC%P z_RM<8T#^a(+_RTD9kdv%>&LO9QuvSA83{u$w&5@LuXEAzgOLGJSw114C5!K+r>LI}P z%e+~)F4(gxxyf4D207MBx{8!{yL;0%FdY4^7MO}O>+I`C)!~-Os6TvXFs#;##ci3$$V^OTYGL>=({~51wDTOIb$7FfaZSoX?=| zEw|{xPm&n7=WX@q6mmDUuTHzumUsKS)rgvEvR*!|uej)ep4jz4{1_JsoqfdmyBe8! zg8x%2^BkWe?`GDUd5Kp?=juCo!Jf2h+#^YLI-g^6%coci~&YY>- zN(hi`u`tPsWz3MYWPZ7`9xOTLjy;tc_w_W6%N}-B3Ev*ws`Z4d=6RSV#>BYOy%pVM zWo3`~oEfImkwF*mHZ(N_*812hH#Rh6&QgB;N4%!tVD|${Quh5nr)mjs)MolPWm~JL z_RsdqYJVlVv5M}PiUdJ-iwP?Dj%0tfm#^*d*(&Z$S9l(8(;rGY(Tq?ZX?fV_jj2ap zf5rcA?MD@|#O5FHo9b{IVg?x#2`aA?&VNnhMSI@lfY5RFN29uwI?G&eEg@rMswY#- z9JKFmZ^Nyv*-OO|f8Vs~zH%th$L<@^b=H7IStVI4!$+_{lH+#Q_txxTQAK>A1`U4M z{4vr*XH|yu>XDL`WTbJ9Q$D%Ew8BzvlQ|mrTA^wFV6pVzUUU0f;nJ11U(cRrFSR~D za6T5c3X8=4Ply}2$F0#UBWYN>8}*ksRUf|1_M*`kqjxAe%Qh>^l=iXenFp)W0U(R} zY_4l44zD9kf_FB=*~~v++vND4&WnW)FG-{(S_388YKbM%_eFunkPFpQDJh^1CnjB= zFm?zyDGz!<0Fsblt6uHmV5Y@`fJCfi3ExGi2Xlj*m73Kiw) zjgK#guZ>kLPxg8amTd7#f`^bC%&6dHE5oNk^s~CUQ9P5v!hK+2g`5S*sT|YCdc-i3 z4P1%4&30YQ%@sJn@^)`n2UrX5y!xX+T*IGXE$k8WddqVeHYd;QHvuE~u`kgMRV5zq z3%?+pHPp6=F_XVEa1!=Re{lE`{^#N?i)8{rlLr=l=p80I@QEOLxAw8c=rqJ?w=qPL zLq1b>4J<|b?DE)|!)lfB#VdfQ_rr#`%$^dXLw3z2er}QhCuO~rwIJ5~s=$_$u9yf- zcQHQQbKB1}uAdUjEf-5Bsogv?UY}y^nFUatk^XKGIdy||0E1TR5Frmm&}9zaabXeM!wJz$%=O|8a?@QpmoOhho8fUUW2_tP&V zso|5TWDL_YyNA@=XzX{G2rn%E3iEoF_xJ~>+tuaD7Bcft+8x_69XxfzifVsi^9#x| z=TgY=wE{-+`K1o&;s3O{QkEH(2LA1Guabm#pO6r5$Z&rg)EoJF!z)%(Sjc4OQE25n2tbgIFx@R(_~vL+zSp&YNOw{D^SRx z)#2R$5xs;o{C}q)npGt*OubhR1=b_;W_!{|t&Q_b9cG6s_O1}VD1^K z{#%wr<6b0%vbh56{oV8#O9)rkju}@4=xcI- znjlrJ>**Vrdq#R~JpC?pTmt{=GIUsNHoSGN(pWzSc7#; zv|$Dh2WJ3yg{!x7_X7OCfnM3f++vz0CJE`AMErM=0P^!D19MJdR3+iv8Q8%R4)^i$}-4iTxE?`#VM>=o#Isb)de&I;H0ar9}}9<$(4axj)Omkm1H<^pD1;5SLgJ zKVN2D!o%5;3`JgCjS|ySGZb$j+`fH0cg-i!A1Kunn1U%be2ihLfC^k(ma%^HP3}7| z_PN6H5Z2h!$k@Kz4rFYxX&C$6FTcRu?w3{P*+p0#Cs~i>%Z1$vLhnhNan|8DZLzVtMde~~ z%Phq6TMtdRBUEV;K}y+8?2O`{i}A(H4OC->PxFP{c8|Neo11U5BVaOCJ1k6{58-wK zm-x=*W0$(Q+>n}uju$HljdMH|=J{Jh?m0x`&Zn#4Im&OyHZYF! zMu@uc|J7`InJV-R_h1?e{+yyZXG%g_fVf6{C0-M>E-?yfsdm|MU-@sEs`6B`*t$Ro zi|VvpI)@Z?D7EkTg3w4ZF6|m0v?4rH>Km?Q9gnL)4G%rAI`u}EzV|`SS02877cS^# zbDE#qaDi5Ug>Ns2Vdl~KYk~wL10*Fl*GihV{&M5%TJN*AcCt7+=D2^k)3~|V97oQ9hkwZ)85P|2dbq#6=(*v?42CXU zbGeZa$r%IRUH08=6k`zx;I8_oaTHZ;CMPKN*I^m1j4@o68LJxDPO6fnSlp)L(eR3CE$% zY0YeAnX};rkl2)!wNnkB1IV-SQnhLm+kxl56CFSSimNbWP!d&b0O~PPV-C< z*5?czU(_8$0f+y*yc6BgvaF?#Hth@j!XSxD6K9_m`x^+VjgnD->EHJd-y z{Hf_p@r2XY{{BtY_Rz(>dZ7mI5mmGz?8W9&`cfV%4}6N&$UIF+H<~F>QnD#qICmL7 z(9_?rdXqSx#h+y=oTJFeb;U1sABR8EcRfgK?nRr@PvavNxJe&=cbV@{hFGO?Pu^_@ z#8jjw%v}=^C*R&n@d>f9r*R?oeN7*M)#kdZ$!Wqp#1@jB^c}BU-g@%WSI=)q=xtiL zaGyzN_jYvHeQRoe2x;@ zpYhVuV8ewp;ZQs^3cYpml$vmzZuS-tuW3#y16?=jLZV8Hzae%x5jVd+F>H1i{npd$ z$*zvn*A1^eI_y1U7b%ms&}sDo&r^k30Y?==`G4w3)V}&C#(T2;CH*+=_1=2T{%-~D z<;-ua;+=u36ji_IUYe+8YS##;Roq(LbIt|67=Q5h>@S9Xj_>@pxZJpU9)a~KK>e+0 z`7+%8+dzo=V$%>RM0Bceprx-GqJTTRkc#d=yVSci;nG=9J9f`n4y~&n_4gyT$JM1ZUb_ zXjE4E#S@s0!4>K>2?K_r@}k_G0MzNv8z`fM2Rj?!PsP2JWdJlc4sf>V&Z_gIeifrl*!sU_n^_hj7nzTdb$YA*2qY@ckZs9QlyY>m8I z!Msy%=e+eCP*e$9OcPeZKy|@a?(n7E#o+g!8i`Sy|Lb4y{nJyq{r3VP@sW~J6fiX5 zD!e{h{6!rxGB@J$%8Q?oo3xUsggnpaZ}tga*UiTLms6}uO{4h^m5G|YBscu6X^1GG ze+UfgTjo}9Pyb_??-x%~2|V0goY*8&DE z$x~Rc;y&+kZKs?gcvtGlaRL`!1ZL5w=04?<7! z)>wskUzE=(=B?qbk?E{{ePa|O0>{xmbV15`VL?n%{KQX-d0xp7}N(-3}W=`+Hbxz2MjFx%aGZ?&UZiH z-=D0^up*#76WYYAh3Br`3?!+3kR_Qr~ zyz~QO8*V01j&2M$^Ziml{-dv;z~%MNPgMl*q%&$5xbD#n?tQg=7$ zSf$-@DK-r}^5k_sf0B>*hmnjj*~-GRVO%Xnf_t*0@qCA%>l35`K@{+_4}HUGEdo#p01gNk`*`*_VIu#1PcKiF|IEh zS+@F4&c|o)F#0lrLC-j zN~+=a7jj5<1t8ci3lk=%d2I-S=fy*44T-S)R;G7tI zlNKt&FGkLrz9YTK2mc+Jiu9nvobi#!GOeY6^Te%Dkj3rJ3c_bjoU)4aGmv zS06|mMF&b7|9poL!Dh_+2GVl)_dbx~UzyGDP8)f|xK|Q+cr)4Qr-gMH&R2fF^oV~Y zGuLuR&#gbqPjwI*%T|h89S>M2cczR`YcqLwu*m_aNhPwK$e(9;uZ~<7GM$q(3hgQ=2=-b;&F5Wv4rPKEFy-x*X2AUHO z9MWCdu7~TF?-FZQ86Q%|2EUI2e^oqe<|&eE<>lFdgKTaWES3q%-Y5jx=A8`Q$ZUUO z@{@1P;K-y)Vv8*{){Sa__wC1n@wcxzE`HW5-DC1IA%<_rNI5YUtK%?sO)znP*H*ce zZt9A>(%Cc}%AG6H{x^ENCeP$CPTccmHer9;)V0>)La9*o=0jPTqF0xwq|G4h#~`o} z_!#zY+jj0G*b{OEPa1QSWLfFWuJ=WI4f%*_=tqzZ@azh(!W2P3=e@!ME8O~xQ7eFy ziF}}v!Q^V(p;B?f(kkyH*>rMSRCt5(D5`M|JEhi zc7qnqDUV9H*gH(x|2#6^UD3l>GJWsf5{Tt#}|5<(Fst@|P~^l^jgs zt9>68|3rs73QV}4cOVWZU*?vdF9?)4=EH%srznSxXDK~BcH{;m8}VV+o(&x`pw-+L z$itRawzjm(=`vr014;vGMca7WL2E>onWUU=AS;v-qsbRRtND6yWl)Z-$5N^G1{^tW zXRQ8Gywo)J>O&dPsGFa;YV~yNm7*%fz2=L8axHfV{o1SPHjHih2z_2-3>S+Nlp^U( zT$3{fpRcs*EZh6t%V$#i7HL#hrja;&8b8g4uvdzFBu3X*#aCy(@BF9Y%tiN@Hu$;F zW7@5+AY(Ve+Av8z@W!Dtg#&=#mGXQf>f-HpWe{_$T{nKX1=9zg9%VJq5;C`7m{lEa zV-{#GPc|?k`gI+OFh^hLMs%Pe=2MiI>M1JSl}`XOcue0Q)aqnhr;&0__;&_FkI9p_ zjo>(A+nzd4!$$)~$}F=dck=CpY)$pqJ2;c~Bqd)JL)KM!E-!k`WBEWqtPt>C{4xQd zj;|VG-~WLi?Tms7_OG>J(m@`OHn@ zjOBq+tyQ2R(O=?972P{nh;SOGW___ryK3;eY73Y2$Sq*R@eaXK_A!%Dh@BWYytL1U zwF~_Ht!QSeQaExsV3%l1oY8*GVE2KiW)w|t#_YWs@8}A(IK#onbHf?M-4Gv8Ksw@- zWC1y?ADwYe@SCd9t8B|!2U69f#}|L z!^*a7buM!>w@o9vx-my`ALp+b*r?@@7z|vEUEx0G zCGBUE^Ch(mbyvnIn60xf8?uGBGVJ}V?bN8g^L@+0eg>Q{P-n}#Iu`U9G;M;rQe^TL zT;$S!HYBU(xOZZOFsTrgT}yj0aEg6DsO`{epUZ>*@-WE9qlN|s%r*33wt<&Qo zfQA+(Md3HU{L%Chkg?$l*KS+WzvhUT29w3<0Vz%@qc&yY3dC8vP$Z;qYtIe~>Vd~t zq4Ya@43$}BJzU)!EaXaSTmkynYe2y-ves3X5NX2%%Uc>#>&c3Z6w9DF?*{glyDso~ zs4!Mq_y)`o-Dv*~q4wR?izVZp0yC7ykH4ij^E=B-kRyG|4DeqA#7lc}KOK$qUD)Hi za;ZI5&fCkl-zR|0E1yR8u0uxdEakxCSXx|l2gEh9EO7V!TDs*oxUE#;>@&xMII6@lcdb|GCF-uRvE1%Tc9gdo^?{ z;-?0lEl>K*qb%036X8-InyKIHg{7inx{L$%Yq%c$Qe6shith#ff>)-mf341U39Kkr zJ$tfaoBu9m4Kj-3Os+g^r?8&yqr6uQ#HR8NZa)A%TIKz|3j5jg`o^KbER&y3EC7Lp zJ{iZHn}THbXl3KKAYICi$}-#44ikC4?Tw%F7G!8~Yt?3gYx~EnmLXti888oO~;rGGqEIk({(^?9TfDwHQ>ab95 zo5tr}C3qIgw>Gjin=U?6)U}%4JiO0-_BJ5CSmUJlC&NFS8haN^RygDqKTS00+J?zK z>@9=TtOC+SO-tQ+Wvr3%Vu3x~zuzy($*_1#yzj4d8lR`D2^6?q9j4wh%$1Y-Bg-sc zvbQ<_0M3M-hWtHWz#lO749m{muvdryCsy3kfNCDy%v}p-6x!}k^(0G{t<7feCP1XPrZ(vm1idl_wA$(`fGCwy~d$T3``<>#nNz@9+h~r7KlkiLNW(W7o1y+9oFURjzj?GvOTCz$7+x|m}rTllWCOlLC)kCr>yrpHHL5dyopNA;IKs7jVz^% zcb8;VB?9C-Q6RcT8X<>iLJshQdImnbjthl(jc#zUCD{p3vv%ggOL$xfj5i+`Z;O3t z4^ZxA0ZVl;yt$9;8pNy|4o5_udr|OmMmT&|67yqx>~>joLWU%vZ=#W?`hB#^i6R}z`;J+JU)41qXR1*!=b$%P`W zj6ZP{O#D853yyU!hu~FHXcdJP#wlsJ$>u48Mh|7?$dFYYYFJoNijgjGZ0`a9V{V6o z5?aRB~I#1E4|^9`vTvp!~YHKEIbfFTfLBz;hFY434(TS>Pf?u zhdhOQ^L{M=SlvX~T)Cz0XylD6#i#l<`0OSyO<^ zQ{{SMhEW{~%E?yL3SNyRSiT1VeNy&X-_{Ii($18m$FbhOo(K5vQP8wc=Lz=X- zO5zu~;Y=hJ1J`apLCc2MCaN8Bz1#}DAMU4!mv!D&nSAjI9863XFuGai04?g7DbF;n zphyv~Dm#1M1`<}UyU&w)=&k4s53&OI_$J9vPHJCGpfPg2d4|1meIcjHAs79s#^$qq zSF+aq*PUIlu34Z`d75PWYy7DYZ;4cY15Fv&k`h%9*vhlg9S2W*dmoj5@`w9Kn&xCp zm034PPWnNEq9L&1)IuvdrK1o|lxV2(;MyUmECFqCk2F6ZABO||SQTp^^t?WYz}Hr8 z6KT>lKsu%ccHY7(5k!@_0HI}vwV{@*A^SjEY94<(P1c8!1cvLPpSF`GZ*zDcJ@qjY zOiVztp|>YSCJhqgZr}uy!|_h~Ki%$oMa&r~Q|`VPEh)lV`0K@e<+PvfgV8P;dJGjJ zLf5k5__kAq}xt6QOX*o^Yq9QZG={IP%3iP*arIxPek-xI$W?!Zw$Wa=n zUuvm1*?z`bW9LBS?OJ3j&lGVNx0e9ub5b2LBR{V(U>d+-k(wFDiX5NHe6Xe)6y$t0n8k3*#`Bi17?89D~ zdL{M@OM$h_>vT`4*Viks`1VS1SDL9h%h{aRXiA{XUV3T0N`BDsU__|@VAkbcS1&1m zZFUo27Vo!eq>f(D7RkI?mL~>#-BFy>4 zy`%re$zzS`G<2=$&>>3`P!-Z>?}zFUZ|d(on%}s(dV5xN_CFUdG3^XKTIRbDHN(kGz>jwgTn6zkKSMTy>Cu@vXZufoFhslD;PA! z{gh0OWClQcm%2t$vy|_q2=2{8FiE(fSAnb6>>zOAX%QF~gyk=p`IZS0qh_|qO*;q{ z{a-$iOm#bmP)?UZ%QdRDJ>Bl{{2>Lm)w%{@aI{&MASX0QH1t+CoAB^eA zKd(=j*9t_}OOU|Ia+m>{83B@i)u4`mV|1{C3EQQ9>_ah#eP7PhfD#_vAag|MTkjFU zEnu`9_8z-%$-nm@- z-Zavher+neY%lex63ok(_fZE<`TK(5PaF$T9P(FphUfN7?^}K#7x@Ep=b-`5VokJ^ z3r4`*5K_?+4Z)!97*kD-px#mx0w5bEF^aqe zS#edp_Oo0Y>VX-voJ8UrPp{-0iU>^@$Cy8M6f|qkx9@%)z{)*Y-OE_>8%tm2AOK!E z^D}pfuSk>Dq03i(ao=0;=bqLjk-D+Jkdc9xf#MPh4jtPAv&7H!*qF%B|ABx3 z1EpF-;~zyNgn+lVz;U8N<&{TEpMvB8<3eb4cb(KYB}#HLl1f*mz8E8g0%OJLiSi3O z6n%m*c+=4ITe;Oo0%ArR^>FvWHeniC+}SV46=A=;oW62_PknN6VY0WvOm6u{Q0^IO zemJ_%4t94w=4Y0;c`ZWJT4yLfBN~iGv`eRi9&A4HjP6vq1(UjCzoQDm!ZeuX_gvt; zti{EhuG8?F9*5%#V7Ym&sy{Kt-Esl)O??#8K6)Q@ymmajXp914T5s3MDu(4}?poKw z)K5}ojTQ{X7-!Xrj!YM8ctYTi5mvYXgDLFSOb+7F2(zV@nNiV#r++z%k=^z=3;a4NQ~BwUI$ARBCl zQzb?CfUHnJqiG(*Gjs!{O$2+XhZI8Z?V!*eP0N%ygjxCZ2x5`#UcD|C7M%g<2>_@p zxA+r8Z~IXpe@mh6o{t!Kd8(irK`0^!kpdxX4{P@=Lc$sxbe(vpuY4|O|6BD-S!ho~ z5qxevGAavIA_Ud@zL0%;xL!CSt0#!ZABrdf_gG~L2adadS+av!Qjf|5sq;6)Ej80w z-)DU#0U4;$Q<+=NFIAJpabRdE?H}~F0TqL0J2k6br&?>Qq?4tV>C@}ytJ7Mw9lt76 zKutq6YdLj?gL}HU8tto3P*?BvwF9iB$;k;xRGq-VC@{m_k5!eEv;wV8h)>!!qWWs- zVy&(Jg4{};C2fs`EmVrJW@&>djgS_4QlEZ!*heCi;}R93-fWxRXBM@)6v8;oYzZip z63ew`N!(zPKAj4y#qSoMItnkF7ZhnLhSCw$oO{I9nIDl#CVaLy1`}wGg5S2+9e1~2 zi1m4e1mw`EWEXzgeN|v+6sflOp5O-_Q~;d#mpl~7A&Nl1(yq}&i3nj(SwY**G6OYR za}%7)6dtnxeEM7b8*G8Yp#MTvD}BYWReitLI%P6#sS(pSh0>LNfq-eOTp7oup6agE zF_#Lo16Q+55d=ZJ>|i-48XJGn@3#{mo(JJt?<5{lF(BX|jM~o@KrmG9s7l|n%mpe} z<`Tf_Zl3@1&d%Yt;ExpUTODTfpQ($Z=6TC5rN%@ejff_RC~!!)8GMav>S z?6YD8wbz7?-OSgs|5xU5iMA$?M$^FUdJCj6JQq#wqT^4=@w|nMd3FGv#RIgnO#OZ} z{dR`pq-Jru!6o*i=?TJSby45=b{=N3=P}8&=Zdz_+Z9mxa&<~4YnO~euCdOgkBbCsvc_76}R5JK5WRK?e zKJc5qTJoqJRDv1GS%X=F!cD_Q%Eg9|PxZKhgRbnF2YkHB=7R2_l%O#Fx8O>9!0rTw z4u@>PctdRHO6YbZKZjIIQ&sr$1CSwSON*m~5M4eTiJSI&O0-{x3uYi=$r8{TOA;zR-?hD6I6~CH?4)Wm6{BQ@9ShUrmR?$r z2n05cQMzNYM9?b>{{NIOEfBF8;^8x2ef_?5%#70XPNw==&(B-}9mXYW8CMu!PiTwrW1)Zs%ju09fe;*Tb`4vrQW+usj~U z$$Ju`Zl2Fk)$M#TYg7mPJT|0c;Viw}bZj0ICx-HAr3l(}=3*C2JI$aSvaF1cMhc=G z>Lh?ML@pBUhN;^Hh0Ar>Qy50)_Rcho7AV!$daho374)bL5`jHSZMtBO8u}{~ul~BhbK1l2= zB;GKC5hv0JKKsh=c^^e)4+Vq(M)9YBx_&y6PCAWZTad`G*5MyX^fhh(up`#ea6xgi zMA_=7Z3UPk-LGB%q5JN4Zh8J9t@Zt&?0i8v_x}D^eaNtk^E#Y)Y%R^beRheGQOm8^ z4LyVUx12Ok+TwdHrGV+Afycqw_QM(2-GCh`4&_HnflpJrWzY$g%uLAQazYDw3piYQ4{32QyJcesvhbsK#Ah-23BN>d5V(? zU&J&(ru-gI3d(S@XvCH+cZ~bfUXp&(leI?b&|}GBul~R^7g>A8w-Lf-%0p97W|yiA z{|Z(Eu_eKj@JGLeqF4Zs14!QKK#du78%KX4&r55txry8Y-ljQ}x^${77wCya; zL|_Bh^RgGgj3y)66+yI2;;`m_OPSvzs(~9NvrQ;0XsHl%L>unR4D_Utl_6vVs+%tR`nnMkox7#jqCf-S$nC}<>~l%|hF?R515$vkwNxbO;`jP<{XR3S307gT+q1>WHSEy~5oT z{UdM4kxiquY68IQ>0kn2oW~)CKuL5yQ#)YVEtH1c&oVqmt?0a)A&g-`HGUe+E-UBY z_sytCZ@2jI-$ND9Cxq?A%liQGgFX-E-8>3JeXw2)w*43JCa#_j3_!;fuHKrLFt4mB zHuy8HKs32QG_PFV?t^SWGja#|pC}{b(^2N2?-iPHFa&};XI!G(Di@-n((#m6h8y<= z9lHAAUed8Uol2AOtLSWWI=0`i9wp@>bR4`tjX*|r!cp<&Rr^+<+Aer;MRP?HY9>Ll zBN8nZiP7asVjXJsmU;(_z!IdIkGRnoI(OPt?BaRmja%*tK%|}*Y5)2#qFdggcFkA110!#y$ZH5D?!TB#ttpTmKbtFq?7bIm+NV=6j@XX3mziDtVI>t~> zam1h5hV#BHK*|(AabiO&&<dlrHnZ7;V|)xua{7DUO+oz z+G};hkl~JSKbyz+t*)JrwdlF2b}WwK4R5;5}!-_a7nDoM2$L-4O;jmZ* zyhVxv^1!H6E^b^8)`E&`z#Mhww%MeZkBs~T<2XJm&?f+RrJo%ZFoX8y>*{S8pqDhK zWMuNdR+t!08g`UHx#pI0(Ys!JSuFjj*tnrU;luq>xinMzj+@t$WJH7gKG1?XFFdJh z1BY7S%5{+Oe?m%U#UV+@zgq<4NS zdO^tqcMMZ9jr(rxNUX?~D0g@?QE*XI;xmHn1`>-Uvtz6Fiu0t-eTK8M;!}$RabO=7(iN{4@(LVv!dARpA0|GNPs%#y= zHu~N%E4u%)!>#j5n=ayb&j)px*+XV*@x0o=TuWft8O{Dd!dTWDUa=6@?dn*_t@l0i<8n< z7jB8z_f&o>*vXfz;yM%`UjFTZfWGAj7VytyC_H${M!Fe5^cL8A{>!{P%NH1V1s+@) zdy<=fOpEe>Xi-&tjZ5itbkG-Oa$h=udnrA?AsD9fb_s{_`AHhl^q%HsXUPBP)^42_ zB4sw@mYi)+-Fe_uY5G@X_7N26WMnSx{)IMCX%2Wt)tbLU`CVBV(9H7@g^3aGSQXEhu)N6 zjC9_#CCd%gMKj9+$Qb~%*utJ^h?@hT^in$3xb4OD;%0!TxR(M2b|ET3I_Nt4gZ?ju zYd6uB0&Zn|!Jj9yGAz*28Z6^!pZsA}z11X-VC6RhV3@gV#~kF=SS#rwO;lcaK#Ds} z;lydHTfc_5L7*F4T_!4gvejF6Q1RNP-zp284mK`8e!CJUr)D24P+Q&gZ~h*&k~Rn> zn9*j1pzpn(F9DBUQ$g383IZ9nn!wrNzUdz|)8Enop(@?+;wxD6y=XfIMfyU?0s<)( zJiBV>w{8()*I~VOZ?#hW1EPc%7T$Xvb;)yB)lWVDv%uZ=KN)Gptb$L}AWFx@SlN=E z$B4nPmT&`ha?p)Zk@Z1H#UT(TdWmEd%%nv|UJ*gzoKXq&)DIc5fY%du1^M@i_MDWcyF1PVW~CLH1V;FZE|5(^Ox$$J``=P z^e~i>L9J}WX|&jFx*QoH8pD?HMHAb_Pl{ksy(f?Kxn?W~EKvh5b%ow{spR;~ib({o?8AQD?ECNEEZhV7e*X(&OXUp7`SIl}Jz#s6czkGJATpLVdk=cx3#96fiZ#d&aw4-dydqsmg7* za>4J=L+N7(P4H7M0RUKM(k;!0k>&n@XZhds77_nq@0yf?VRuRXa9bcp3?tgpL!&vy z)72Bv<6^iX^`qGtC}+ME)N;kc)%JcFXp_&d858+6l!lGaiOT&6u93UMXrOZ570oWv zCA7eDa+*YMN%{;H*y3589|{n2U|G|@*iQXna`MreUx(tGR>{{h%znt(nb4cXxS4jx zcLaUl-hlwV7RUlBpx}}0!E{p3-W2%1T&V|Xv3Qy=u)b)}c7velJYe}67{{6VNL2Xm z+G*eTSUh)%uk<%FE`Bvk)1aW9MYH^=wA)QrfYQHQ;u)YL0vC-mZ94bq>Iev^SXn=*34)8Yb54hv*9@4{e$B7PP6zRi}zRwwdu86HsXsZl(D^qtG&%)4^CZ-~;JD zz-@JCy=$vNEZi$&Vm`sOecEj0 z!9DD7v9xJ?uhoYHHX#Q*5$4L4C=lFqPqv!m|21Vmwvn0 zlgua-MPrzrmQzJ%MkW69HVtmP(KI*z**QH0_VmN5r6SBV`+!7#z>Ze4ZviTJ2A$VC zmUpna8W~r`77Dz+Y2x7~UV6Gcmaml86S-@w=a|DTr_e+rW#2&Kj7wfqp(r)6`j|BM zNyv99#c(ybTSfUzEaUcItUmUq_T=AEUeN^2x2NOQ2MGw&_-W(~Q6f}Jr$GlMTFqZSGmYuEq8ql*x%v5bXGs38~99ocmKG*u0?)Its$( ziA2Br!lU(Q3$GVR;d)2@`c#6a3-rCSIO@?BfDR3zUh5qB6dwuu72K>L zj&2RGtt=C6q10(SkmapZ)Buz8ck8Wt7nO zc$ZH23HSnBl;eoikwT>t&#@eUyBQU2rw!f+HX>eQz%L$Bp zHB-e`s^SfV;Q;W~*)3|*W|S>puFA+!YP76(!VE$+D%f-A*?qkoZ|2NAEL>r)0@?S( zO9Cv_VJ2%!&1L?uxgcpk#(TntZ7+#z6)lu`Mo=+lFZIsuCx`-4Z)hlRxLU}wpU04^ zI6rD8xWr+wZdbU0yM;I^DMOJ!gGn4x`zkeF#0cz|6xK5tnZzYHauIyrj<#YAVDsZGn)CX)F)IpE9{g z+tZYCRf{sXm1*^Ae325xYd4@G+YQmoF00fxXBwv;S>Diux;`as>{GyA==@oIwn1Izv!8p0-d_LG`aKPVTDJ$OC3vG_+?q5ShOq$AI>`lFbgOYg; zR+@r|(GJ6Nx8l1Oe_OPC0yd!*sz#S(T*b#P3zrtPB7Z#etQybz6v`0*l|=4Jb|uOm z>A(u|!`)*Pdhygo|J3bwB%odNw|fu45gC6X55SRgKoyht?qhcU*|`l08UR;Il8)!X z?EOM}viJq!x@kW?g?X%_upzXbi(`XduD<*7HQs`Jpzm%w?9DO{g$p9%I{fA9;P*E* z?7kznhtY!|x9>&G(H{tp*4GiQAbPz*w9{qT%6vO|iZA?>qoD)#a+-n-c^2ldAJ5$V z7z_Vjv+S(72b|;Bldi-H#hkF@Z|b2UHS{LF(H>2$)cW zcRAV(ry0nfMQ>O>mO!vnJP5FOd&8P78!A(uI-uwvic(?nR%^08v>U)|o0vX&%7!TE zqn-8fe!|29spFQ>4a)pY(fcaH`#PTNkbwz7vZ^i7ObGT@$o0Ty;CJdNc>v>J7o`7s zjK{8>ORA;9Z18CA0^@?K9Y7@5Ns1;$1BPsq7G+O+E)S-naOA2ZVaSLaX4;^4Dh_RT z9JO~wxjEuM%L~9428Wr@A8}OhSu!N*^69CGWl&s_z6NU5cE>)-tfYUDFyprLD|XSn!|f%Pi!u@qFEr`yS^5# z?a-6C$`N7!SI#tj_=s+CTOO53YCjh|_}gQ_;w8fu>e|Oq?LZ!F+GLWKJu1(x4*yN! z4tT%m*6{s#SoAA8j1>p#`{8?i1NjPqQCja!5Q5kWkN1`f;-9$%vjQ^JmoZ?94kh`s zeSH+Dm=@vL%YWZn3@}eehuM44^6v1IZ@SEf2uC~ALPBwzXHXLMq{Gij-bk(gEE&E~ zyZ#kVa2J8aw%oD^yX4j2k*|_jwi9QdN~&QBhtNe0aPy>p`mVx8C$s}J`6eSJK}d)fzfuEH zi1!oX-PE-JFJtQ)r2f?Y@V_IP51S8g(Kh% z{Hk{Us}W)K*PkVy2MipBJ@ABlhU43THAfdB8~Pfr3H z66Xh>`6?UX)T}^M&{Ix6MNkQ%xPL@7t?j{<=Si;smm~DfK}Nzcf9JGd){Ip;cK_K_ zOqkEjeQ#7Ch~qlHpm56N2FfI;1IZ zw6)Gbc!NC}(Zs^y`nBsf{9E1S@4#Y_n+ylZm!l6p!-Uo=U=Pu=O0Lu)ea0Z?iZ(ox zz52V3OI*=&K5#8lvJvG*?Pxb%j$qJ~{~0vVu>NPJ|05pAU2kaie-x1r{`^nYV*%*r zEWjKfyR!we7mWFLK~&- z=a1C-$RkVqO`ZBB!KNyL`9A#oH8n{ZTwrlO#el#VgkH2@RW0{OV9@B2;UcvYU!h%* zCH(h_F4I;*e6w!c_oBe%HKYGYcGPQTA9q3${;3!o6s#sW|6}|9WBdN&8rQR&|Jc4w z47mS9PX8~)sG0Sa7yK}X{KJ+EV1s5kV#%Wo6$C#N5k5C5%MwHw(}RA)-v~_ z4kv@n)by}FRrkMn?TM9XY`{}tvnQMbrQ(NbJg=X@)A&fAA^&=&Rs{bHc7(PMr9b@I zj}pkPSuy;9){r#%!2kDGx>$incYFh*Vqn15llQ3@z58fvJdu1M%v9FAJ`VBe9zPh} z{LX0d4_u6qmVg~jd8dVzpIR8NWxHIcc7_SrXl02{^VXY7@~`Kx#3JW@NUnz1OLY;idAUOQfhR$3 zc{0g(t!*4nn7%Fl5?<(jkOTp;cm*IL$EwJ&ayU_Jpf7LE6yMSfUKOoy#Wp)M3yw+f z+TrwdndE(1M$bjg24E@}_{k~RWTl~inm`btO^@%>HX3e7>IMLxcXv(Xq(Q`Vr4<2; z`y0=jL7`Kg=$SL}g4M!=d>8P#|Gu;0UPi&2T8zcRSLlQdf%aAbeJQwGS^l1GOfmTi zlmKiaj$J=rm2D;`!&l?o+d-Hbb9hMNK9OcZ5aft*+#=aC;kF>s9?f092@w{C3P$8e z)0GB))>k1()RR8N5posd*Q9-vQt!q&MI+#`8^eZ3Uc~VFOS|0mMnEAr0-7pW3{Q7H zr5li*vm{8_5-MS`$u#4$UW_Ap=`8*;3heN~74Z}B_t_6g`VdWV8(b9k1ub>)ZU0N` zi6mRYb&m69fBqQsgPK{C_a0ESUex}V6UaUWzXQ$ku~#&>jzkHDFK4rww2o zn_3{ktkbiyY_}Cm82)|s9^>vF3dvLcl;psYNp*j42J~T**g@?Rj-R7Ot)iefMh&!Z zjmB>eN-rary%>dk?R=)(UuYQ>@`iqd|6icN?zwq2g9eAzmB2*D zXJahkqDHlB_%RoTns4uRN^MTx13u-q^W}`>6B5kOmL8bPT_ockg2K{kt<%P7UwQni z3LdG)SCKza|W*X+~Gx?f@C8a&n^*P53I$0iY; zkK8gBm#;Bi@-d`vi#?SMbQoXsVS6K?)C6LNY6R}_QF!REAAW+mBI197UrF;bKf|fp zS=L;dZW;eN$wslm^V*{Fsm83T7tfQoIhQicWQ%$2;D2#rfZ9R=BPK z5CilG<7i`|MB|T7zhN^F{aySa;cq3mhTs5Zr{g>Ef~W$UT2<4HWB*IOPkIv}w=Yd1 zp?fXq9`N~!G^(0^MDgZPLEE(zyUEq&fFBNnYBeieVx3&P$6U`TMB>jwCJv_%!j{CM z5wjWmFE{#^enY$G9!N66FsH|{O^KZG_ z@zbpD>3GC}Fu|BHGm_kG4~;DPV^JAhPRsmPm>sX8_v>N_ssA<^8+3bD{Q{PwCbxe{WfEm8vWbmdULOXG2Q!9VAl-c*v_^$> zc#ZmovmP0w`J0*w$19RLNaGJz{yRd%uj$X^>M@Fp(&;m?G!?Shb_+dcVuz<4*#0OO z97qL9*P$-(l0cW51q0mhvXA9&P8|#Y9<#EO^Hqrwrdo zK9AZw^NK%h1!Q)w%}vq9_m(3RTAl>48cjJS$p7 z`B42oa6hWJB0`5~Sb~jUDsH{5yZ%P<>|G>fo(t67X-{pNPcu$ng{fMvg$rJ*|M_fC zanAS!@(>>Qm^5K+T(|r4*>T?e z&8>@-LCn6hPYjZpEbzX7poH@5$T)Hdgme=eC1UP8lXoEhQ#2>8sSN0VdyqIX)fjiU zX!!C?*nYI}GzCnNq3dDkm0{tkif3}++BdGXT#df;Usvm|df0|o{r~HszcGn%hbMw@ z1ro)}?{CHTx;xKx8np?UbXSc?G)im?2Z}Hok14BWr}WTGM*bX<1<4T;HT)0b zp=CDHx5}CR?jvkvu{<2Z$%3^oV zat3wQU>z4Ujd2JUkz>_hlr4O21wH=O4GKhwC}LOLd58!I!xSAplEg;x2}L`$$!Cco z0O!r$k#G`{2t+@02W6kxVvnz16W(23eZ!g$o6R0iFKwVOE>agw9#k9LWHUle!Yb{h zuL{A>t-KtXF7jVDN%+7KSsX}DUw7a;LGHCf9$?XBaRTt0(rekoJQY1oh2O5CZ@GNu zcSAn3Z|^zAr~Cxlk0ZRt?Tk(}OFZ3pwJjJ&80f2)RA&f>Q$9le?ux1*=_6F_Zc?>f zU$;US$Z*2VgGk;V!vs7wZXsAT`~`8z?`~zuk;z3mo@&H0S+u+N@2Y5h`9bt%pu_rr z;kt8yAS34~j2ZF2*=WMdZVbcsJ>j%o;)JvA1ryO z&s{!c{e@46`*&&6z%KpXYkzc+^lwLd4mKHZC@OjGK8Jd)XkRM9YdYL)7((Qv$j^c3 z_A0R1Th-rqQyCZN6PmKlnh{HN%<6Jh+4g1jZ2{e<7_4WG4Q`t2dNh72hIga*SC!uh z!lH9~3D;6I&sg%=-S8*YF+n(bXjPeulRc+B_dbJ4ZjgJqkZ%2HiD4n3GY=EY)$YDH z5W%=wQOSw98%TaRt0d%x(#6xhN`0KH8TnspH44H=8f|V^Jm)J>k`j)tyNFHwx9Lfp zl&8SRQYtp`-gN1>&WM{=8P=BkS&i72N2FIkHWbOf5h7WzRT{8%I&=<@!ax-SWOQtE zpklhGMMj#jbbc_l*75v>YJQ37J9eM;4dyuB58bqMoQUPHude)lCrKTtp#k_L;R!Ysd>r9^dN* z$Q~GNP%H*cR7dOI3gfj1)_ExJ4j+cJb@0jsU)7^#=oj>q@y2)%e$r0yM?}bK4o}!p zcQVgWj>Zl>e)6zn_0E^_eLKT*Jfb)D0KwIqXcw;dEh+q3Vp3t<-ag9V$JD{yB*n4` zWCPGHlsx3Npp!r`t)TG@YxGrQ+KMcC3Q7{vYD=K!sJ4BzT4zdw^xY$o>zI*uH5u$Ol}`NbgBuLf^erd>?#c%OEI|mhyEZ!Ss9-9UUDK4pU9K)Vk`gm zkjO8!;6IZDc!QwqB5rr-Vt`adOQma8c(zk#yg(sm@y*!_ZG}yQb2^F7=B#g|Gnkpv z9L#^7Mwn&{gr5#mD!Dr_s8v9#g99|a$KO2eDon`(ay}#&IutibMlgaxq8eX1MK%>R z-_4LPbHqM&81JKXcGdI9+JzR%XeFpATMCT1?J?fr0;O5yv%GwT_QwCx3<3f#k)J z_1lhuYGe~J@fPQCALAQ#=%}7Q6P-ZnPsK*j<^FT5{diNFq*(D0Rd-9{QfTMXgq_EoYPE z4|(w}HqVvB!DYYszcW<7^4Cwa=E;77vAvhYGKG4NsSaVzJ(GIpy}v zvbeeNW@%h^qghOB)Bv7yM0}v)wk7)7ojhHRmmRsA2iJQ6D5j_q`hv38r-Q{S0cpg=U=->08j+Pn|6UQawno1(@4}>y!B0A?l>j zL_XdG5>}Ck9`MFAR^fC9e|$g;5m7 zBA((8@Br6rZ|opS-n(oNpYLxM@P=LafS{~2Q%vdL!$k2f#+zi5?4m&C^{%>!-d$x9 z&UTF}*So#-9e=#Rc5ZsOiYKNKHx*Rh(WSe3#*z5GQJ@75BmvZ<=fRMxCfjookJfmN zsL9a@X`1kmS3H@sU2J5u0oC918!QRJgkS9jXtS6|f3*pjCxT4h-&W}EUjEefmiql| z*@R5K2d%nGu4GCn+;P_IH8C`ZLwg$N_D8*iMOGofM3;FVq_l0*zjOZarj?M#A2lN? zH2a4^X08;6%dRqBjJMJ)`uKzR*dwqc+(JhcnjjxZOz>$IB@9Rf9hNdFtb%fZWNyKQ zX0wWz9b%d}9!{GUUImc=F$=lK&l7$Y;`8psPQ$(Q#WOQ3&TrR&6vQU(?3p`6IDti@ zZXHv81$4|%?!+pX02Ln0SD76Z!et)$8ZuVv$IQTeF^(|6Z}!;~>l3vF^Ohpjqe9`i z?Wf|mPZIeF^B0HCS)!WIJI{`6?>!9Px)n7?S@F3K^XPT!@4fquo4;i2@k>QGQ4&?u zHddq>p7P+b>z_o1T@o|4)|t6vXs}z~kcp@#YhZ_ZmDyDgcsEsd<~nf>`(Qo(rPjIy ze}2^ZS+HYm50%Z2r<43oHpwwNvf3?2liB_}6GszqYhWsKMqOaBnxT^@%$LFWpB7QB zJq-ab?GO1J;)v7`3L=?xg$u^~nNzUX^HPf&uJ<~xlG4U2KAzb-?~u8ISxC0?so=bLE)KVejJ9h}u!&i2{AVUJTutr9_sEz-9W#;PqQT zh&SM9W4;Y0p+VoM>*}QdPv*Ra=o7e2Z;_1xFOny!e@vL#IiqejGk?FqjC7T%^TgCf z2iYw3nnltn)R+z1s=WRlwD2CvKPT`evXzFBnT4ut%ro;okN#%=rVoZ~SkldN<3%d@ z?UwpSPGVQqTG|@$t@LPKqs@|O)ftPAVAPL2~P`M zdg6lQ8RZ=m)R%_-yciQ%jf|Co*3y&%6U!{B48vtKsPjB;Xv5iiAVNaQ>X!(Y%dcxC zs&UPLWt{WdKQE)PBuTe9XO(~#{VcRY{g0tKr}{HnY)$o-zO-2Fku;(Md#qwK*;8c< zz4MNwUQ{FnU!A{WeLWDOu$RA;Gc)OvfBgtBTBn`!_1O3E(3c$94cS^dfgBs@LFfcB)rDu+mi8glrvsv#F zAzqZo`B!*-K%``9itoPPOg_$+Lns2G?H!WS4k8#{Sh2SaIYj#;Z-iR!?6YG*nu&R` zEoQaxPChYQ;q-(1bA5;-BTPcv16Tv6sKOXfrjWFE zzt)~l{1#x_b@oGbR>#NTKeG8}`q6{rjY&K&3m6@-jGqcUiN%RZhkYWB9FIHl{-pBYNX0REdtV5GOvr|a*hH?T8}(QOSTT)~N; z4aM!xDM`M=P%rxQ@DL?Lo)u=tjkC=MuAAK(Audht+?glD(&`Wwv!(ElQ`=Zw{D<8X zGZ}g;PjJ(%fAoX^6`rpb2#qw9CVQz>0vZ_7AO_ zCuQ?0%TtkLExA`%WXK83)UbUt7SkoJ{dGv`xO&wZPX8Q-VUKOpB2U0y+upY!5f|CR z*gB=xGW#x6WLPWZe|Q4BGUj@CD*4uw@DD$t(g`b%NdPr_W^!{6<|KTeFwR4M27Sfu zYVsfwFXlIQx8VBbGUxwIy!qn`G-diDk$;VTiR7fjP^1ol(60t{?8G2dRR?{z>+mrP zAmrocFrIdzGV0s?(bx6RqMvd+n_sP*Yv5~^?i-?>mTL@vwMZ!q@q zpNR1l>R8AW???6u;)qbmq;2ait`_Xh@xeY`^mgYojV1(8m~H*P*n9J6s=x1lyu6xJ zq@+QnN{CD$^OPhqWh!KfGE>IP-B75gOqa}Lo->p>b>PRKlk%rn3JEK0e0zdrBZ z`mXg|>-+hqrF)lVf?CvkPW+P98Y2k9uh zWaIN&zYi#&2&%H2>!|tuUC@YAWsQAlkP?yzT4WYvCK(FcO~w`kB?EdF#7mZ#xaxv&`7Dqgkg%03Nk{es=8TKLD}s}AEs1I0R_`3retf~Yqm9ygk}U7_pi=DKnN!PfEAyB1}O*qZ9mPbYLAYCxvEen8N+j4 zQfn5AR}vG^x}AB7C>W~vLu*nGmZs0!u(GJ`Gj|6D3{{4ZNDi<_h$_%`%0JU64mp() ztYTJAB^RX6TbPEPGcf4>T_n~OtKlf)>_lfF! zJ<)5)y_40MW~vMe%}5IycvkEs*I6gx4544!oju%>`%@)-7?g~{VfNwUF#2_6lwr z;cHAs%LfL7L=5S&Arro&hOh(EYx&_JF#T?`$qrZ9W&(KX3Q$E==-(=D1~T` z1Tf81-1L+x+h59=?z3w5pl$&f{2^n7x&eQ<-N^)i$oG*bz*xim&O3TIT_+zFRcEwn z++7Gl7`-049jrcuysru-_WEZC5wIK72xp+Ptwj-$>K_d9ZoR0mdZy_?WLP%)nf~kZ zz>q}5J~FG4{Tg20=^teTFO+6S)#M6!ZVhbhs5W)f&}v*Ja3_g5hO%EHIT&iljnmX{ zS&p??2;gqd%E~SZxVh*2Xy=v(-bK^BI*P^bD>PkC!cL5XE(K=3BbxQHaPuQ%|Yu7dbNK=ZW>WuF!{FK6Xl+~?` z$Ex|L-v&qe{wo9}S;F^l$~<#_HIXeVSB(Bnki zuxJQUq`NmDU@h`=3Aw0cF0*I#5L(AutjBLdyW-&64NyV#ZlN~$I?DWfKMi7Y&fN+- zfg=J^>Ml0BCgG(3<)YTU{R4Wmwt3$fR20zAXve&1UZJkm}^awV3`XrCo|B= z4Nv?{I_R00`qyl$QtO3aU&L#rH#N`KX&z*~O2Ka#zm)kO zzxsVj0>wZP+H*`j4N&MTN};Ra%nyo6BdvnYN(j-f+qJ(fiUzr=XQdr}7KCABZBu?@ z&EMbqeZT%3kO;OTrs=JhG9A2J>51~IA1@d1gE^M7;-&xj{3de?$$?4q1U@O>%vbz3 zQ?e9pMd)*yIlsPQ42egnzE*Y!BTpA+aobalJ9+=VXyL#n)jo4=-Q zIl*-3-fILov_Dpkf(Gp*>}$E!H3j*7|KOmIGrmQe=mD#?yOn@+d;)5OXaP^1I_6^X z$3WnjLUt~uEF@iY-6%g+M}Smnkn5@wM}GSZPmcS?ECkGfV`5q8yf++vH$Z{7>iS#% zGU?Y8bTkr8Q=v+zq6M7+#ZvWF?VT_Hp}5|7?7wc`eoR9SNiv(9lx4!Q8K7>c{ub%| zZLnVl+uB@yea!^7$13-7;P0nFyqPasn}U9bNi?GI?X;7A+M{ce5O@ZZ5FTuKu|b^f z3E03_!*AZWtq)xw1Y?4hu9d7bd8?7Db@VSqijtV#fI8G0ruNgmxP%niWE0!7W=$yp ztLT1sznUXhKaBwp+=wn6q7A&V73bf?0srK%wI#2A#g#p0l+tL-EGavehVq#*nO&tm9dm#*SlfhL!qh(~`;5c$oM1-#W;f{4%ux@}2 z>@Pr}jCy(5_f|Hl9*Vdiz*Ix7!)rXxMRdP=ve-Q&Lj=&Eh2f zEiHBA)ytlr3XO}4tA8ILbx%MNh{-9~vKE|W3Vd5#Eq#QHENJBE&n*0Znruif4lggS zMn|DD+tq45lnH2GFB`tt)#Sp7!yLA=x0~`E9R3nr+Q0{&_OH@%`1&ARO)~)%S%&-R z-0Dxib^gB|$C+o%c`#VF#aiGYVeiIY}O?lpsq(%w*s;;ZO9&|hq)~8xdk@V; zB7mL1`L27ZYi&#la@0u?cc<2DODOe?bU^-tvGkC3<_pSO)}H?*VFJS2ppF~4wT%s( zOu|{*KTtc;Cs5t4oMoP--`x-xA93qiJIN(lflA;oVbu$k*v-*{`5Ya z4!d1^)|MM_oayY@1NdY-=n|QRwGY=3qZ+A0c>D=Ay(o6Px~1jVb9HxcIO`D@Zb^XL z>N#9@LH+h^rpNB?4H3$QYg*?QjSqrtCF_(0<)t>fk(HVW%GW2~?kIe^IDJCkPxkQN zB`z-Rgh*U>=9-_ojs~bRpp8?d)1l(}I?{fB3nYAdKfE|RKR^Fbv4R$gK(FNzSLg6y z52aLvXiz}cMr%lbVh?P_8M%awRtBQ)mzS5Bn3$@6Y#UPK?fM(THOQ%sq`Hs4D{FP~T_2YpHqi(O(B!wQ=O$%fnO zlMO#N=^bF$2q(;@))x0`veIl2M~{^d&)p^o`j)f#-I_$AWN>%&dFhe3ZLq535D41% z$vQ0ZMfv?yXv?uG@H9DbH2T~hz^>@$Z$pJ17NY*y87r5nw2_CzkZ{b$0-Dt{&97Ms zgec&^xuBu09)f@Y1ma=cDUu3mKL{sah69>Rwj%&^dB1fn!3Gu*K%i`MiY;q!#C24C zyJtHT{bM649T*z2={O#YOAw7<@`ZOst!6Dl@>X{%I&TZtT9WQ3VF~(?82U-R!&nYZ z9p#YBOm?`y%1+rq0(pu~uy|dyeYjQ$sz>>hhnE_+50SNK{e+FO7J=M8c@W0-OZtAk zmX#4HWy=J8bDnNP)RhZFW2p1|29eAv&?NfVTWEV#1hkH{*Q?7=*hpzCV*>kFqbOZ! z8yl&g%Fc_Pek!01R15r7;#+z*3N%w4t1crX&+46LihaeOA?4(F!A*ZIM=J7ev^W3nX%|?1)Sp2JZKrvR_R+6QG zfdO1V|H+~Pz#Jn!t>e7~1=ZUyc5B-m?J97fNz@*jddIqe5IYp5?a6z!dBJqJ?-2Aw z%Y~_b8Q1!Q1)m~*2t&oW4YGvwz3bHC_i)3i=YOp-PgxR9bTj~Ifl<>Qzl_WE_&s7v;0jUH&?IU zDdmMIoC`(Lzu-?B*N2LSkV5LAjPDrojeAvxL`5ZQ8`g3P*p@#GBj`Eo*@B%)sDtpe z%)7LM8{hr&6I3c<$J(7AT(x8WkYp4s5;4$K(%TU6SvTS8P~g6Y>u;s~zIzxLgXCey zk9%R8OtlH7dLuX)DhXkGfC9N+$CEyc<%Iu9mJoG#=KfA~T}IZrgp% z=6R(+>_WEz9P^U|KmPz<1yaKKThrFybbq`j2C;R@b(yh~>c_)9U}>D5v6Wi~xnDn@ zdhk_Z8gk@TWB3~uitIrM0uXVZy}lV{RuZgQPE--r$mrT0-U0r6$E()57UNj^{Kp;$ z}5HHY<-TgN?r zpauVjgFHo|czl%>|J}@8klcJKO7FisuaF?KZNTt1S8ESbAotr#cf^wZ7r%w`$`;P6@jDs+HdplryZ#<5U7ON>i z-DR*=$M=8sbJXv#eC(vFpu7zw#UDa#E)rpn;jp>%tH{s3jS7=)p*&4$-09!;>0gPkewU8 zTl*vhQHgvSOPi>~Adn6#y_Svc*{}5q6u>jv_q|<@dsz3nwb=gem37xW0EZ{aZ~Hle zCIX|pxn51`1jScWNI~lVwW@yX(f?Xi*vzX9`icU%WSbK)ys0JrJIno{KmHAu{_&G_ zvl386#6+~^zdZg{6QoD~Ca0T2p|S(Z)5-+a}u1y*2SN+?PK=%j>`H?7D4Q&ogUxEdHC{LO>GGd!D{& z(1HB7bK$SF`Zdomn7ON7fdAiYEv8p*p+s9DR0XsB8UYzyr(;I1JDRkOH+v(h7n~md z@BJ%(z37jJQRSfQOx0Cv+$=ZM(f=Jgc05YbTBurV%%42t4{A?kELS^vew zi$<=;4I6LWf_2s7li*#6*0n>_Ot7sjBzJ^SmWzf7Su1w(<w-HY9 zo%E$}cbrjnUsr^hQS8AR;-^7~}|*SX)IIM#2?CR;%9f84o;!V9+jLgZW> zUJgr*4!pnLiujEY1x!J9KX7vX)tzC5XKEhHoW#DKKQLa{gmVij2h{2Kq0CLrg?Eti zA{^41`fC6Z1xL$2N7S&3{}$9Cc!Lt$iu=ospkk5J-tPY3CP#HD1OU8l9vH-`U{*Kw zsV7VWNkwB{XFtp$j67w>CaUuLAOSRRMLO)^>aE5N3=^UPs?wt8`X+Ah_f1%T*%`}3 zLd;;tULJG3|2HAlLO}cpNdU0Hw%!mV3N+=HE{Xl?A9?^QGCIMXcK2E>fVIsK;DXTx zIF$YtQxdod2O=#CtuyumQJ@S+E5=9B^S3kN5(0oXyfTFS8G#M=TQ@Gf4#Dqdgg5@g z@Bh9(5lA7Fv(Y_&Bdiwy)T6!<%51{ya!9KjT2C43HRAmKhVN5Cc>yhC?x$@MBEV7r zS7^REQzU^}mPTs8gkt(*U3{UQ}%YFelCzc=*4#(TdX zGJlX76^)1(SyJyGpZ~j8Z%ajdB(a7cE!6({B5VU*0R?h_SM>vS{#w6AY?RSLd?ci{ z%|^ES7oUVd#=zA&#~*tt3EOPf&!H4a(9kIkVHd9dH1i99{2`pW%l0>&v^g=KB4Ad9 z)CTOV_@8F(g50hj9MfBC--AWpZT@Nj3Iy~Yua)q(M9StrCqR6=x~_VX|1?RgY%HJv zVxm~4^fyOibIMcb!OWvEah(5M5M-@5)PFbg|7!(YI_GV;2On{UQR}BW$Ds``?q#1d zxbJ-#_g(g7zMX>iK{ZCazazh;Y~x<&Ujs@fPfrQW=ZAp;N}?)L`V$J)L{t% z>LOpcJ3H>1PXf44S(TO>fqQrszZJ7Vl&St*6gDI!E!eZN1)-^1u+bgQFEikZK85Ol%3mlXVa-+f&7AQ*f@gnl&WQ`Z1I$xmet?}I)qn*$B59C9iTG$s}Hp3y&U477; z+UD+i<+iguK8qT2W5BihM3c1bJ-_shP|7J?zwzAluJj~3!&#VI%eU9q zIN}C!wM4_sdZVU-PXt7_6K^+t&abYTWu_}4&p-3ow1XvhPJDJcGoQ4iq$E;BA2__@ zSo>_r_bE}Kc9PpNU5BO(8h7u`Mr5eYWHmagt;{!`*QVc^H}*sp<$w6b)a0a- zhi&)Cj#t4;U*$?WoiWGk@}O6EVS1x#%eMh;QR#1$+@l6&Z$?cI-Yuq6-49)&`2*9s zK&bCmDNYsOs%DLs4eDb!5pc&R|uZdj|RNr1Q+UX8DFp}o0AK+Q6 z@vt{ryh@sDd=Ouvu%?WkT-(}TPUKiNdLsh~hywqJo!xa4tC2@>5p~9qQzUl;vck8g z>aR-izUl0@#H=jWC)pD*WPeTV${!CB@WQeDd`D!qUxc4H!?@i(KEL}kC!$ubrR&wF zj1Dft(Uyp-w(zM@^2W848^W`dmU%2wUICu?=f;Zr`*mgu59bj;Ul*U-!2X_zmkRmH z?j&_?n;vZ^e;!4CN{WDU*?VzDv#aFEaVr@a>Lkh|zC5m!Z#>-jY77CAf80t8X*%9m z_A&8Pb;jEj<5ATwx8f&OJRE4`9#GcJo!LCI)|?Hp)Xhm@iiy4T?*8ByYhe zGdJWJ^)8BgTX6pT$4h!ET)&zuG*SBVo7JZU7f_yV{D`09M;js+8&r1|k38bG*%U zVAcId+2A$S6|eZ}b1WTkovRwHd@QAYW1_j^@v&hZMy{=RNpte=j&hkr6J0Oujc!O# z?z6{-@&^XZGLuWi_XY0Y!d$2=jN+~)`4m4ooIN_%<<2e1iY~doab*;e)0z!n)Lwwf{@h9&IoF=SxI}ZvVx-b5{ z*XfhQO&mkG;j%R*0d1P+e zc-`~~^eSo4i`u$%o0!L2=n#GaG>?yAONN!?{MT1i7LJ@jCA$>T<||WgzUMhfYCqdy zJu>1zkf%=8TG_FY(K|n&V<(d zQ$c;FE6yOl$Sg?*6N`2vg_?X`@9D0JlNffg?TDe#v^S<01Lyq0la{jk&v+O$~+Y+7p!0#S{{+pN4kf&w%M( zueeR{_Gw4-s@Y9>_q_z~MFzZv9^FSKJr9lA&wkVta2ubc*)ZnVYzGLCkKmD~lZ+NuW%EVbq5Q*7j$Q*WMZe%MZ7FBk*P&Uq?v z^p{3n<4<|Z^ZIj>wNfgw=AhFeZT4}0x%ba_FNsNyz?6MtyP|b4@Ne!(-ZP}i{K1IQ z0udGMDfez2Efm7%ya0qcJdoG9GkdB*`R44mjxmXkDVidF+?I(^^lDe_m*=!+f@bFW zg5L+(jGPuBvx=xoQsa+`rPSNbwiFn;GP(3}Mr$d4Fde45G~pf8g^l-I2q8G}ut0XB z=2>Muo$YQX(cv8)S>{7Z)MvFTNjQhoYU8H`9e0$0)u~#ons}YFMl?Q<%bZr{{j{q> zMYU)O_;+WHhZ8_?0 z&Up8;cCg*|;~~Sar5p_KF3C>}cbPk>-MdP`jiTIGyEkZs{!DOuX@MX9y=!tiDo{iU| zE!XDJ1C|?)ln{t^O8q4=hq<1^R^JMC_CUXR6GsN(#7nmAx&k7t`t`9rl3VhZ=Z9KF z`=5~r>~QRl&0Bt^C<0U(UqP__W(ENPvt`!%|F> zoOetdsJb#kxW{pT_e;q!W3!?ySH7xYuI1Uj(RqE!KHp*Pz1rs)i$y-x#}VH5C+&}z zEfaszuL*ND8~Lm_4<7iP5b4o}AQg|=a~G}`(Q&!*+t+*(iec5v7*eCRmy2$-WEy8aXt-CKf*~7H(vuIFWeTx>U^Cg@ zv15nkv7p<9xMy!wpqunQinQk2^A9(rgomhQ8nt%!zA$FER@Lij&px=sk`W!+R>by} z$f9Q1I@pbV-j3x?=Y^RdGW}W+yY^-9gDpp!=kw~mIN5s)ge`#D2)u4LRc|t0MxAXq zu;|ftjQsAGt$zFtdEALv#U+v?BK+a=q)jg`AD)@;(oG%%7JAihDYzI$^v@JGqkYXn zM~w(MX}cD0YdiSxWO;M|MVZB3z9&M4)b`P^k+HMG!PCI1iC zJZZ$J%~H@XbdTzvpy5DKN);|vR^OZSIxTzQpRPTJ8PZ00xpM3!J-7yGW|m$C%Pv)< zVl$JvWA4p)IGn+D_WbzlpxfKHq`cTy$_Wm)?(2kpmDHRRALRol%plGI zM96mG)+ZgR2TBV}gw3A#+EgaWE&W#L!2=@}w@lucwwgC`_PqC<8Sd*#G>km|M3X9q zyWMU^<>OM+LYQCw@MALGPs|Q4`4YNZ`6UT?Oe&l@%`Arpk}&)EZ+(5^*F8MV(m{i9 zIz>Xb<=gP#q2O7a<*-~$oI~!oAyDx+KhgHp_X7*@d*hRYhfKxaRxlHmCM;1}pGth;+U$<2&p{R4!)_Lu>E}9-hsl9Ep66R< zD*RXymIk#cTZH*n;JT%mcvBHEe+}Oql_Y0eG0s~iHW?2)jEwJZX<+W}qsTzt_Sd|B zP9gYRm)Lc7d9}}`;?sVQhEI;ANW3_wJIZ)xWo(FuPC@*$@RzJM&92P)KB~_IP5gm} z#kXLX<^&m=EsM`;aSTIB2$fk+jVLcoW(qg;K$Ogc;q@0BjFXy-d*EwE6?SjA zfC9&z+*EfDTlgCBFB$f*tDZBY`gUya$YfA=COKgRZn_;>lFF$I3>n7ZnS8QFjfbSY z%NJdj9_)UVAHo-7uibAkZ_if1phn0~ZODRBz&K@)5{}?O@Be%yCtQ1rS512gqV#IQ*;x^q_zgj|- zMNNKZlK1JfSP`oCV3w(6o)zoNP_h%G!;fj{EPh0&hM#Lc7!y@q7bBandkd8pC3I>V zOUNO-^=&s1ZE5tC@OR=|__eK>9tvVdxGm!Q*a$236Y`Cv-Ap|Trv8k-&dd7-hX|Dj z2q~WLKY3A$Mf_8X-pv>|sIRpQ*n}#+x}{*_g_o2w?xsv1XVOzXvQQ#UFk|QOpfyUN z^yBUe&Gv1Rd{)pPPis-}5owZwixq@FZp4j*E7(=rL@OlLxXIcRsqh@5RII6rSJ);N zHTXbb$61X`%J9gU=eWI2%Lb!hs3@o;eb(s{;9Pmb{N=LO^TmV)=u_2R6({TI|uH?qr%Ve(CBZZb{6{EbS& z4ED?gB$;mRb1Nglaa;p-+!bN`NxndXns#ztn`yJSca%eir`dRS+Kn#AZ=F2W z1z|vL8xdeKBPGW1H$qJakvC!kGx{j{mhMdk9{{vuE_qjIA-&TJ>wWVKN7wp7Fk z88A|+=5HR|n~)LRr(bBieW}(iY~~j4)r83+Y%b4J=+tI9-ludY0vpT0X98`w-9k(v z)Yts;(Z&)u4Wc0sV32C)jm$!Q1|uTqj`(&`W_d~;LsvS%0+NZtT`wUR5+>+qa5!)= zCKPpuUP+6mE?J|f+nsxaK(_uw7xUt|x~FAmX3a%au}_CsH7v%v%SN2O8l2}$Atkq) zthqsZ!X-wb``nyL*R^&tats`S2(&xv#hax)bZF$nU}w^b#k*;C-Ts)0Mf1%tjqS3> z-;VZ_wz#pN346-=7iPhT^#u!uH&tVbg$KHEKE~|X-ODrk#nGhXsJ2TC>+`Xu6g(un z%3Pmz-ua^0i|2arye#-|jX8ra_x<^j82FLt62CKn!>J5$=JVaj$;Ch7rd#ux8RRa{FM|7Gmh@2aQv>d2!AA;S zHDcsScPLw5GTrA?Qv3q3P7}JkSyGQr0}+P2SC^u^X(mbPjlskoVViD|Yyl&UN+~7w z`Wq8Ll@t1mxPamc4d5~#yacvW>rhBNY8qG;Z1y)p>6dc$6?fs2^LXrUC6g5n+i(wvCgfpPrBiw0Jexq zrws9ZPHW?OTFHSkN@e17=xy};i{Bj644Tgl%=oFk@RZ~#TOz9)J)he+WGAx%AoY@^ z$2;t<4)rOv@6Z$o8pFQY=9Riig`ORs;*Nsgoa`qCn^_bpH4#dWF(x ziN!O0&rJue=$K0NVJQ!d$^cH(0;@yMXUi8-{7bC2UobKDPuuqyQ#D0?=IKWtz+`_b z+!Lr36j|=d0>KfC__s;kL!_e+qL8%WaV4#ICxdZPvBktIB>1!3c;^yJ4@#k?0Ir#o(N*ugeIIa4yruKE$xx0_FNX?eGqOEo9fnChZd%FN7eRdDdE?X)u`!8_5iO1IS$mCrH>jR+&j&pswOkCx zy}<#;Z*q9>lomvUWJw6|zmW=ISOi~bz1W#|3r7Q`S>i6s|2{HR;iKl~Ec3Wp@OvXP z{i+4O+@Z`13GR6308VRa3Hw&&IJHg-s&~&hY`R`?Pgp-QT zU)@BuA z*EG=t4>WO3D`b=9x4j21?d#s7XWuc`j|YHv!%bz-g|627*uKwNn%8>r8>uXYP2MbE zZlW)WXBw<9PAbZ;X!WSkYj*RKqMR3{A1WK?))T+d#JCH#vgomga_N&>FGpI7U+fyV zNHy8V9qAs|Zb%jFYi6*-d%G{ta)~T{OSTQ-eS)d8H(fPzv#)1Iwr9K481#p~}p?-Di)o1&K z;f4#BWyh%Mp2pngL^@Z+W&$8bdT>RwZ2Gu>+8LY1(+;n zYgUJjHqMC(7CTIK_w&o=pX?v<(kqOQq3uBDhmj8jjgPrf2i-OsH|n%0GH#Wdxns7X zaR^UVm!jO5#Lxy@%c8wq1B5r3L>zYsb{xmM@%2;s`&Xk{bdml~kCpG@x+;5|rn&c7 zj!#tq3EpS49!K7&_e3Q(V}fXlb*oX%*v0k~*1h}Bq4zsnk=bs_`dOVe^PTbY$+-wo zPuo$<{BjL}+BwfUkSVjF&q&I3^6p1F=5lrS7=@dpNnASWm2$6k+v!-hPAwmtJo-5O zVeaxAafNH3_SY*yOvHDY<@L`iQ(3TjyW$9$>IIF26-eTx7P~V~M|UXYdfZIy)?^Y< zS-Kx;{ox~Se~j;rjFxnLZNJ!rm~-CebYt6(TJJl;X%Z3MZU;-gd1?=iHnIQSSH|<$ z9HR@=aHw%zzIcAkYW;IxFAGDC?u(pS_jO!6U)V@=4P=)N?vRb zP1o?%BD`fDFXxckX3Vd&M178vtN)6l@Bjnxu065ph{0#}&@>EsQXXX~nF(Nf>OEl6 zVwgGSZUqjgHkVfa@Hi(reCyu)nLve=OY*0vchS&%3$0K8sPcTfDL((=Z4pPy0S3sK zPxES~T1k+~^Ax!wr=YwG>$iZiuzY2uG@Xti;>fToi+r9-mDN=;Sp}g@O~eP> z><)F2v!TndDdMLKVAq+f5Z`gy)UMmzEsIop;B0WZwDkTfc-tRJSHL4RFK6(UFL5apI$xk55F+pls?sZz@;cdDG zN`FiPi+Sz0BJjP3Z49_9AUGEY$?FGvCA02Hxzqlm3lR`08f!hz>p_bSuV))V zlGJNpx^nLFXj^W=oqDEL*O~z`A-dX&o%q1M5S0HQ_N)qPK*)?w6EE{v zN*Y{3Z-^i0w=_HWNUcdIuKK0yDcUiPG+Hl(#>+W;5b6>8O037*exGxwF=+;GMHOt! z&`S2`T8QQLDcgz14Me=6+|x?}ImuAE9?B8fIJ|l1peef$A}6e#nTaxBUX0Yp-SM#; zrHBla>Z5FT_v#!Gq#mM@bgI`o?RRFT-65aNM>2U|_O|V&gN;d}{SmDOJ_-p+ikQg0 zT-&*E=^}gZP~8<&bw0noBerYy zH5T*inKm01(uP(^#~rSt+<0vLl$~Zi(f2vQvrlyw@i2xa@dG68#_i@q4V+42r^jr; zGaPK;k1esq>lJ@-?nA0fc9|!&wnX@G!TfJuR4iM)8=3?Q0`@TGFeThMzzz0TaZQMH zr;RZGdq0M`0~Ix+C-ZmTuy&{u<7z%*oiE1vB*E85z0y;P&krwDyXCmh0}g<7goW3K z-k8Z?P|3PGua$b%powXSYoHJ2kf6KI*BBaBF3l_UB@&H>?UL1Hd(pJOyFBK&k8js7 z@g%p95L>YQyl(%UMJ{||*3y|-NF(($2A;zbxkQm1pb5v^SPm!r%VF_s4_c4H1l(7p zE`QfznU`WjalCF1Al9r}(S1{&Ms~jll72i@x`v?}6;dM4GcA zXOEX`H&jX-HzS1-Kc6Ae6qFI-4XG|w@oNRvbC6PemZFJK8XjOp$8wrLZc_OLB;8c9 zEJuyfqx72c?6MbUyF6s#6cXfAWb^DRm@zK6d+*MhLkLg22Tw}5+6|Z9)sI9!_j8_| z4YMF~9It_6%)ygjxjqMPr}fjjZOz{)qzce7yb(M9I#^`L`5<1T*wdWF@%KThP{PR@EE1bf zsfx`YW~~bvi$|z-oyfJGax)g2w*^E0V}vMSoYX;#9*$Jl=<(dBQw&t-laO+kG{|Tm zx9Z+sGa5EqEQe1>NbG7{K0AlnNk(IRX6%vbN#2#@NA`&lE_H+UpvIL)t>R=K;6Z%Z zV$_q0hl=Xb^A4JoOh0>jaUw4L$KoT3kOh8$4)M^AcPHg8|NO?3*RVMro$Lk%C|?5s zW8Q2Snj)z_D89fc;GGi(ugauq7*#VvK7ieBdwczX7? ze9tHzNK=fSu8+I=D8@%?s=nO-F$a-M=tkWvFtQ7>E`j<5&#;}H$Y-Y{DL^-I393-X zVi;{k-bA7EKQgfC)wvi;&R?fKsK4^gwJO+d+Kd-!8gnmp{`n;gB*py63RiLQ|6YNx% zf(=~@A$i4=8_t*xh&LUQtuLwd8E|V)ua>^h88M~RwG%8EotLOM10U{@*Fq48w$R@d zn4_9$q!Py~FO;{FGY@98`f^`*E-1x|72V_Gk;7EJn*2xxc3y@Cjp&UyQgd zOVmS>S-|3JQ%AF$kee$?QEI2cpT-E1eRER*4W6QggTI-@Lh}Yk5aoD10Udtz{L1sJ z9d|l?uY_NmQ($!+C0zMzEMuPKAj(UFo`5u5`51){8oZB$LmJ7|RoNrqWh76;YbZ!3NO)_C@xy{V?rvgCk_sKehZpQs6EYwi@yYH{ zLM}vj5pqeYt%EV6C}7eSy?uHvC(z+Ux@n&z z`}NTJtsvx-#BXWi5o2RswaqwxDV|nBNw6g8`9&%VB`XZ$moN>AeCd1be2d_Po1aSQ zbZyUw`IMIWT#YD6eAE#%{6J$;u+ADX*%K}j6^GUP9uO*3A~J$F zM(Bfm^MU*8jcM|48$84(jc+Fp#rKiiC^1`bgsA`lUK-uJAB#L8ffMQZZR5=7uERzg zh=PV<=DX~+n#;sUGCWcH7?^zYAY7F+iB;PoN>{T~DAV zkUWs0c3{s@_Ev8`yLp!#a-F_#7P~4?lv~tL2z9EJI*h#O>kcrj#N|zEx5)$@97c4a zkb$_gnwH-6a-^WkyE|_Byg+7vT|Gto{1eI<27ZfU9u8@rSahpztG*QT?uK(>+F-d( zR;mnAQN6l*l*ab-vKoE9=EI;uoF>{o4f&@zg`e18jtMwUy0N<2~<{3v!;5Rn)XoD4nLW=j;!0)Pk_cn1mv4 zW4kG27t>2-K{n6Ji|pOqdx6Nm?n@8YzdV>I%)^k|lpb>HZ_If)h_M{C zh?x^|0T*0`KENer(tebSmKHQC?A^MP3=z)SBg=0mwY=~6aG--(&#DTuo@c~xyz0_@GjVO9>&A8n$^ORQB_O#IWc55 z@wT%a4^b^n5VpQn7c2jMQ1v4~hRE&GOJ@OFnlvZ zGJCd=Xbj4bqdRpRJpoB@HwE=MP#s#}`-OG5Lk#v5?-am#lm&gS#{i@SwDoAa-E^}E zcu7$ajr5Ib6&Ur(*LS)i)F4*%2{x*I>5LWetLrtQqmK?VPCpLwj#~xXckDaO>c*Y>$j&I$)7qaNATpht@wDGp z%sAYV>3dbpjxFzWTpwg6Ul_l>;_xa%zmcJ*3GQ$lr~w4apGum>8$nC--0X|k6fLp8 zv=R8@ULKSR;kgtQXyFgJC`@f{W+!`$9JTBadf?35k&2qKSOn@J?6Cwk>N5bzoDG!4 z|9Y8;`f15TVerbL_EAV#MA5g3{X=*1LY9kQeQ5!TGAP4VxeQpajU93o-Jyw~ z5DmZ{uJsu%hp+$*{AiW$l zH1|2fFFtt=a^3c-2+a0tP=(`*?)yw{+r7PeNx9@kawa7AnL~K18$8ePw42#s^)#vu zOXgKf4)e9a-9Wl;Y1u#@G4c2A&cKsRR7W=_$BI{wKSwB63iLga}i)vFg=bQi#9SQ6S2uTbr~Sr4W7> zlr(RblVX{#dL@|0-C0A4o`?&~necd?paK_jNSH~xDOp27-W?*>nx=^MTjUmqByscxM9Y&1M(lo9+X182;rB;FMy<#Y5997vV0o zU53a;+-qx@gkJ%oVlkNn(zBIlpRXnn))&r-i zo({-GLml-{Phd8{2c?WS0x-lE&JAlzpAO=JfV~|!=UM>DkY?D@uzO;MnJ?hX9_H+053u*$rpqQpbc~o>Uh?dlkWCpM|k*ke6D^9@I4-Bmp&x3zBE5GI;uON zd>q{sb^aJOQ=@>S?VpVbe^vgRsh&b)FOZ-mSwqOvHV+0qX9Id!B40mO#v50ALQb<80YXJ2_J$bd=}pMQdsee0)h30*Blq1AME1K#BLU|f>*r^3W;6DIB9Pw9ytAfjR%=q||v_^YATvIM|PWU5Nl-(Iux zTH%c2z_|*VnFoAf1$q1glQalHyj|)o+EYwa(VT0Osnaw7gIH2`K|EmDkb2% z?*PBgU|a+lDfI{zp{_7M?!d>g%?iU3k>`6LZt5=AJ5XP)MNCrRyLxqQMt~D&vmu`cgwD*2ldN?I<$GA2!sI=Jj3bj;CZ1&s_ zjTR!kO`N8fd1bD3J`)VX^UcBdddoOj>HNGqWRFt}+aYN);vukxCrJ@$RA|Q~q?(<^ ztJR@$QR*77S_IzJI49MN5ns$G$sf*Wkui#bgpH!UGB04}{5h%O3H2U_NJidpXUp)b zBdKfETY)rF{P0wh>fMg*amHDAa5=)6Ed!S=g8Q+t17TBgNyCf)*X5Pvr37YwcH^K* zf=bXD@yvh%^lCWrrU~rq4-HlW3!MPMqpvGsBb-iHYKXhaSjUG%1oV|nb~g7Jzyco=+ETFrrMro=a{8kml%AQ zw`A5Ka$Z_VbE#vW!#9H@O*~RA)&%C~=J@lF$q&$aL6kEMkV9pRHjeG~B<+Rnd;j1k zmyq+@+bR{!=O#_raFySz2@|e_LsFMiACw5Gtlg<06+bu5h9)Ys=k#l+Tpq_JiE$^b zC8KWM!N0QjDt(__yT5=$>WBD7adQ&=0DMxeYfGuRyXFH!@)e8D2)2lfaSP3~ztWa^ z9|GH>f&YYWqX2EeDW;()er;^=1k1VqJv%2|h`{$=+0L2hpAVPBhie)}pFTyNeJmR( zezfW?*WBH{%D{$epebf1*3ELGRKhRmtao@B9V z*JMcnKwdDpV|Tv_u_GrGKhJ1;0hYsVg7SDPh(R~-O=js<(^i~kqJDh9ZsVDa`Ur`K zs7V#m$|D;s$nOF`a~9RPtG8dxvk}+Fbp@_)gg>rac-`gxss!R*KHBlnK|#qbEmr*8 z<-{GPC6AA42F58lfG5!$U{sI+l$dFwcLyU=TypVi|NQe)mv<*#$>SS~JK$|ZM4khWLieUcs8KrpDT&}ul4KCf%+epr0dII>;x%u;Om!-R0#aw&eHGckq9j21J zQ*uM#S_en_KuAxyYv|4`!V1oBZ~qDB;=v@{zm^qT9~O<4jZ>h1pCTJ2`Ha)6M7{D$ zfg^qGr*wUhTe1IaUpl7(;gsklusez`P;WfaL+WtB%rW;3AjF0RMd;!tNue{|1rW^L z!xW46Nx3>^;mj%135IBL5}W9xl*7EIc02@guh~&a_;(L@j)(zy7_$0L3|&ums|L%f zmRGoa*}ic!I^2DHc&PCjn()?A?8!8^ayZKL(P-j@iz-P0Ny1~*K z#Ly9Ol(~}LRhiUb;=RcxmkC#r;0O%0m;h&m8(`s6260_T_%JkMU#%{H9vWt|_Te3S zpaQD=B@m?+x*H5-=_H?ty$P2z8fL9qU*Y!lPb28KhKH}go^98BpRVryfMYU%aOKGn z<`epkO+&b|Om37xvK1PHK~ms$h!x=ZeU5^QS&E}hMN8c2DKQ(GKY8X&8iTp1@B z$h91JQm0;B!g)7Qyj`0`i(#lp68PZhOQ2dwtrz4-qs0AgJV}~KG8^%Q>fq&9wwVH= zSzYDulK4e=`T=nMrbzh>m_+EdoN7C1wuA)1Z)hc-18i`6iYYz%)$NY+?OnDrt(Ght z`Zk{6;w`u*$#1PyG0L4%&__b8k=)`Rmsho*R1RF)63x79d&R+YE;sXRf;E~WFLA-y zU@DvDD~Nae!6j_seG0_eOvSkJ&}QN-gCP3*)rK1Lt%EpXCP1>^59Ubmj9t*L0AOPg zsVP_rl744z@+4Wp1;n_efs!}G6{35O$4mkC*mG*GWJiBpj3-70Lk-=dM3;}qGes$9 z8a1%#048KSFlsDz$DITG;b1mMXgXD74Q?BIw=|fw!+E!+6#&9)XEKpS7umHoqMgj_ z`VD6$r(?JVyBFV$A+aDmma3s`raUB)2MYoqI9#2@U>%s4zid5PdG^`@qzFk9OnmOP zn)4bk^#Pn?vUM4zzJqFC$0uKJGd(X`vwtJOXyg%(y1u7ZYg3P!h-Of| z5<+uM`oT_@o68=a!ceRG)F6%pjxG{I#<6+*L++K)Ps*2t1$sC_02#@g*Kv){gDRzqaMJ)a>ST9ZiBu z0rTcnu@clV3la=cnfISP*nhy|YGZhGd5TGoSQL2M4YL3?6dn4E+k5v7SaOTtT1!GY zFJ3-^_Z>G1HS%||k-j-89NPEvnh?@Bg4`j9M zBjah(Jo+67DkO+POS0{bu=Nl^I zc8mZwR%s)ROhLjiQ4o;K>l$p*aO5O5K`aF}$u}lF0pM;%-MP?~2+@;Nw*RlaE02dd z|Nm{NO{mtOaz(YSl`Gc_(@;qc@2C%fPMKDL?f@B4V{URi&h3WFyLilsThjCX_i=ZhTP zeBf);4Z;)A2*>H1xzHmP7^nUDTq65NNt5zkC3$6!>CtYgoX6`$J3tO3gFrx-q##%o zRZuc|=i8PhoELz|#~*dCB{0jlZERepWe^*5@FpvBl-uBL;C=+_17bZsrFG3=0?vx= zA=7kL+da9EqMrSXifNgD*?j(&Q*R^}jKxjLpVI(!s;)oE-Pl$JMWUK+c!wV?*IP9= z-5$sSlVj0K><9n7{2c6DwfXK-*8OVgkU*kf($ToO88PNGpTdbk&)*_eFrt)ZOy*N` zIQx>Ef$Fu0?VBS5=7E`zg>bD;00LO~#pO(pqIbB%WLl=eQR>p9K$$b9cY6v!Vs%X! z$nAES4B@@n`kwFZsAjvMp6IN7$>A3CNvxucV$e zUb|~f(Wb{&wL0C8yYaIF-U}~EdSY6&zTcw5F}rNmP_rWTg%tD>(TE&(Q*esKM?cOp=k49zqoY_U zmv?z&#O!ELV0-HA?d#xPF|-&XjV+b?PlTHi@I=jT&tjdg8-)ENuPOuhX00;%>&JbS zP97Gm)QM4j6Mb@5Wah)UR1`)5kLm=+XK>QM4}GiZ-wT9ynlyyqy3TQYOhr^?*F{S% z45P4AnV^-K1)CW6mv%5b(aug%4nzk$@k5)<0`Z+2TnIJJjG$x{i@?UbDw$Rezky*9 z^AV!gLLkg|2Zyt*RVu8MtZ@;Tn}h15f;rwNof&5#;4vECoKvyFTjFI_)(Vqa|FsSa z3=G`S4-eSfdRJIJvS};-C_=URTD*e=z47F!@zjX@A8@F=5s%o822LQ`r+OMYk}29b zktSlAT+q!m)^T>#})L0C}wI?k?xbMH4G*~-_V=AHHe zi2%2$11y`Bj;nKI90t&Cf-$)fvpfB zucF>}G7_);{9HLm{Y(;SZ72hZJN-*qqF1S8O_ zJjgD4ddqqx*-nlj_=j7rW21M(_^h~(y)Dcr3}BT#29EozStmYFoJb=YVVft2Ul^ff zbt3y4KxtOP!nb66wWsl>gRC%*-Rsm)Qf@FpeB(7(5QG}h3Qz|D@$4m6s(&z+sp3Z{ z%t1q9?5D*V{gqq{N$aTjdiFD(7!JT_xKXoT9L$Mma|uniw{jrJ|Ap1ZT~VQmAKK$< zsu%0e;MESj3>Fw4GZ$$NN|ORvfz2>jpTfbnMR7%;j)WqhE;Z5Y#jOO&@zu+sD6T6x zSyWeOLd?@?bC)i!H4030uJVYIBXO> zKEEGIkC)D;DaZgv6fgDeR3-r9*gGX3R=HvErn@>W7Wl}ii3`<=)WseanJw9V-dm@Y zPyE{EwG9z)G~T@0KhebE*4C1g#yhm=k$+TAKts@DR9I&R>XO!l@v$gNEBFQpw6L0C zU}|{u4EE)rhoAOi&*3N{)1g;;as~cPuk@Xe5go&JbfC?K;d1RjRfYmDt2hv`YNQa+ zGjtP0_;qiHYWuPck;h!3Hh^`eWEG(YWYv$g6ghu-?YQX1+Y|^Xe5NmxmIWA`0Sqhm zmBXK9le<7Of`*Y+qhbfh(7*fPpZOqKcRerL zl6zYW}A@=(EVe&@!#{jgzSS>4LRibvEXgnN<4>CL~8Mcwvl8375B z3;r~*ZuVmO<2foB?;UKp`=c}+oHWN*WcS1L<8arGh>j0= zh2L6KM6ry2*U}^*k_GWVXp~Xo3w)+4J|g@OntM6wQG;SAe-5ji@`6FLwXCnq z_*r~yl*28_?pcVhqL*hZ^kh@wbTB&j;a&EsdVW7f3+v7*oYu|sIx=bO24ikC;*GT+ zzq@88c!FDz?hz?-m!>Sj9`DjaE$%^LsC`vz{e90HwFMAtHpYvWYs@7j{+?E1VUl*EWN_crYy*&BeeFf{K zmBH9?wBLFBW8Qwp6SYyfhgoJj`mw+id=bE{xV=zSsuHsqstfg9+%k0tsX@#1&u=oKB?sm@DtIiZ27~wGMdt@U=@o#(5$?7@MImL6U9dEtg=(68Qs=}5|K#fMK_$n2S_x+l3}VB;;fmg~*atU7 zGSf7eYDLLD-#i$CqaxBLyE2_^AXgPV*lL#EK6feB4qD$P{kR6@%(VUu$r8VA9hCAG zet=wNby)TCrmcl&QYSY#HBxL0`#wfMtLG@Ns#7(&e4Pw7+X^Sa2m6gaZo)0SO9>!H z{8SayJwHZ!et*BwLb)p{1nYR77O~^p72hd<@?YM1H5UqJ)BHF#UV z6vk|G5m&>$evk3}bhpeMat??Es#f136j(=|J8|R5x;IHOy6Q&BI{29#vcdtxrlx)8 zWy6d?4?_^Q)gB384Xr~~34D}Z9^buD`>92Ch>#9tL)I*XI3pG#X^?*eL5SR! zXDGG$p<0-vhwS^U_Y@*)o30`@xQR$MX@H~E0eIX8j@e8S>*x5b$8jOB5G?AP+kjto zMR`Tg1*A1We9Tl&8^GI-yV^_KD~VMpa}jAI6hJ+-&gJiV{xQNH)<#a^H+ETJdKcbPDp2UhBke&; zX{@0&XZket{OZe!$1D_I!Ehjkc(Q6)r1}Q8@c!g-`J`tVnFbW)#`7(hamjgPtwU~h zY#J=tIspa!TlHxY)vGcVc6E-Qf$5xBBo;F|Vh?)=s9u#U*s!g3L|v)^WziTG4u#EfESe*! z#)72ko6@uW7w380oG{# zrajaoV-B{Om4^+&`A{jfVS!I?z;yD3K)$VYE?<~yO+P6KyOLimX+8A)S)P3g{&6%Nj=W9#;lF);0B6`e zm4C}bp7g_TLVU>CE5{BeFPx7Pz6*3hVA|V#8oSJczC5oEeoSXC!+k0{0z2ShI<9q9 z1{x*7_rHhQQx4h9-y>P*993vE>%j`(coS>KlzBU>m+LXl1`-p91SF+r{=Lj({yb~}NQl_^yOh1o*%!dsz%!yjCh{fRY?RvK zDSigkVoeuN2;leYqtn03BN)7_l6wvqzutqjRK-LZ0dpkh9gYii0yUUzOe7a5b&e{bQH)$8$gn%itLv` zV5DDz)+6pa{Vc=N;097^8Q|C{qb3N^jjWDgmYWBlfxLMwVaK=J>eufHzqKcy;ByDp z0hSZ|?cs2%-2V{J3GLVj>tFVQ!L-EL7EB`d0dzN*kZ1OzHYf4G=X>if$n?!@u7eUG z3BrJ>WBQqAPXSCQ3b^TH!IL#Rh*~ylBj`jq`jvwR(FKV66tK5F+!Fe~{qc%#(KMm$ z1W>vvi+V7Vrfm+t>INvrf!TucjHe?^f1M^*kuhv8%%0~G+9l(i# zi_F(K73SdkuoU1J0{c>InMUGf#Df%tDr5YfAfU9;5b2d9wD$KwZR^PbA9L^2NDI9n z?~kXu8Cv zJ;vjc_%3i%A%=jMC(?S!cb(8p@s4!YfXOrNF!gN=R&1E6jEgvlBh9!1=Hbw(nRP_O$O}cG5zn5cxv~p9eCy zhhLB2XluM~{2@PGp=$B32Q-vk3FEyuu;5#r-EIrGkoZ27!he32 z1X~H?i{epCpP~oEU;9*o64Mv3a@%&MM_ivDevlv>1v_bep6&PXvFk0#1Nqb$AeBdo zB3kuE3cfy_FUL$lZrYw@r1U1&>=!UJYD0w`@zw;8!#iPB{zeBZ|3nYmL?wY*+TF>T zUxLX`gI#t=G8YMjxZqSq$Df!;d|b0s*r0{1U$clgGyFqWmy+Bx;PTD@N8`K5Vo)<@UBINLCG8MS| z50B+!BLrOF{Q6wazjMk+Jg+coHC$0Q{rj;d}Z`#8aK7O6Pb@1!B2H{BdAyeYN063)~ A+5i9m literal 0 HcmV?d00001 diff --git a/docs/v0.4/images/extension-example.png b/docs/v0.4/images/extension-example.png new file mode 100644 index 0000000000000000000000000000000000000000..a77aec0406b793ccfede133cd421a1ece9f9c6be GIT binary patch literal 43264 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�(qNuHf0zA9j0J1e>4zT2U4cl2-GDX$flhq>gq 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 literal 0 HcmV?d00001 diff --git a/docs/v0.4/index.rst b/docs/v0.4/index.rst new file mode 100644 index 00000000000..dbe107b61d8 --- /dev/null +++ b/docs/v0.4/index.rst @@ -0,0 +1,34 @@ +`Envoy Gateway `_ +==================== + +Release: |version| + +.. image:: https://img.shields.io/badge/slack-join-orange.svg + :target: https://envoyproxy.slack.com/archives/C03E6NHLESV + :alt: Join the Envoy Slack + +Envoy Gateway is an open source project for managing `Envoy Proxy`_ as a standalone or Kubernetes-based application +gateway. `Gateway API`_ resources are used to dynamically provision and configure the managed Envoy Proxies. Whether +you are interested in using or contributing to Envoy Gateway, the following resources will help you get started: + +.. toctree:: + :maxdepth: 1 + + intro/compatibility + user_docs + design_docs + dev_docs + api_docs + releases + roadmap + about_docs + get_involved + presentations + +.. note:: + + This project is under active development. Many, many features are not + complete. We would love for you to :doc:`get involved`. + +.. _Envoy Proxy: https://www.envoyproxy.io/ +.. _Gateway API: https://gateway-api.sigs.k8s.io/ diff --git a/docs/v0.4/intro/compatibility.rst b/docs/v0.4/intro/compatibility.rst new file mode 100644 index 00000000000..8c835a587b2 --- /dev/null +++ b/docs/v0.4/intro/compatibility.rst @@ -0,0 +1,23 @@ +Compatibility Matrix +==================== + +Envoy Gateway relies on the Envoy Proxy and the Gateway API, and runs +within a Kubernetes cluster. Not all versions of each of these products +can function together for Envoy Gateway. Supported version combinations +are listed below; **bold** type indicates the versions of the Envoy Proxy +and the Gateway API actually compiled into each Envoy Gateway release. + ++--------------------------+---------------------+---------------------+---------------------+----------------------------+ +| Envoy Gateway version | Envoy Proxy version | Rate Limit version | Gateway API version | Kubernetes version | ++--------------------------+---------------------+---------------------+---------------------+----------------------------+ +| v0.3.0 | **v1.25-latest** | **f28024e3** | **v0.6.1** | v1.24, v1.25, v1.26 | ++--------------------------+---------------------+---------------------+---------------------+----------------------------+ +| v0.2.0 | **v1.23-latest** | | **v0.5.1** | v1.24 | ++--------------------------+---------------------+---------------------+---------------------+----------------------------+ +| latest | **dev-latest** | **master** | **v0.6.2** | v1.24, v1.25, v1.26 | ++--------------------------+---------------------+---------------------+---------------------+----------------------------+ + +.. note:: + + This project is under active development. Many, many features are not + complete. We would love for you to :doc:`get involved<../get_involved>`. diff --git a/docs/v0.4/presentations.md b/docs/v0.4/presentations.md new file mode 100644 index 00000000000..dab818b9f06 --- /dev/null +++ b/docs/v0.4/presentations.md @@ -0,0 +1,10 @@ +# Presentations + +This page contains a list of presentations about the Envoy Proxy project. + +| Conference | Title | Speaker | Recording | Slides | +|-----------------|------------------------------|------------------------------|--------------------------------------------------------|--------| +| KubeCon NA 2022 | Envoy Gateway Project Update | Daneyon Hansen & Alice Wasko | [YouTube](https://www.youtube.com/watch?v=3MUOZc8XNCc) | | + + + diff --git a/docs/v0.4/releases.rst b/docs/v0.4/releases.rst new file mode 100644 index 00000000000..20d05b25ac8 --- /dev/null +++ b/docs/v0.4/releases.rst @@ -0,0 +1,11 @@ +Releases +======== + +Learn more about Envoy Gateway releases. + +.. toctree:: + :maxdepth: 1 + + releases/README + releases/v0.2 + releases/v0.3 diff --git a/docs/v0.4/releases/README.md b/docs/v0.4/releases/README.md new file mode 100644 index 00000000000..2ca374ca69d --- /dev/null +++ b/docs/v0.4/releases/README.md @@ -0,0 +1,43 @@ +# Release Details + +This document provides details for Envoy Gateway releases. Envoy Gateway follows the Semantic Versioning [v2.0.0 spec][] +for release versioning. Since Envoy Gateway is a new project, minor releases are the only defined releases. Envoy +Gateway maintainers will establish additional release details, e.g. patch releases, at a future date. + +## Stable Releases + +Stable releases of Envoy Gateway include: + +* Minor Releases- A new release branch and corresponding tag are created from the `main` branch. A minor release + is supported for 6 months following the release date. As the project matures, Envoy Gateway maintainers will reassess + the support timeframe. + +Minor releases happen quarterly and follow the schedule below. + +## Release Management + +Minor releases are handled by a designated Envoy Gateway maintainer. This maintainer is considered the Release Manager +for the release. The details for creating a release are outlined in the [release guide][]. The Release Manager is +responsible for coordinating the overall release. This includes identifying issues to be fixed in the release, +communications with the Envoy Gateway community, and the mechanics of the release. + +| Quarter | Release Manager | +|:-------:|:--------------------------------------------------------------:| +| 2022 Q4 | Daneyon Hansen ([danehans](https://github.com/danehans)) | +| 2023 Q1 | Xunzhuo Liu ([Xunzhuo](https://github.com/Xunzhuo)) | +| 2023 Q2 | Alice Wasko ([AliceProxy](https://github.com/AliceProxy)) | + +## Release Schedule + +In order to align with the Envoy Proxy [release schedule][], Envoy Gateway releases are produced on a fixed schedule +(the 22nd day of each quarter), with an acceptable delay of up to 2 weeks, and a hard deadline of 3 weeks. + +| Version | Expected | Actual | Difference | End of Life | +|:-------:|:-----------:|:-----------:|:----------:|:-----------:| +| 0.2.0 | 2022/10/22 | 2022/10/20 | -2 day | 2023/4/20 | +| 0.3.0 | 2023/01/22 | 2023/02/09 | +17 day | 2023/08/09 | +| 0.3.0 | 2023/04/22 | 2023/04/24 | +2 day | 2023/10/24 | + +[v2.0.0 spec]: https://semver.org/spec/v2.0.0.html +[release guide]: ../dev/releasing.md +[release schedule]: https://github.com/envoyproxy/envoy/blob/main/RELEASES.md#major-release-schedule diff --git a/docs/v0.4/releases/v0.2.md b/docs/v0.4/releases/v0.2.md new file mode 100644 index 00000000000..a0dc0e885de --- /dev/null +++ b/docs/v0.4/releases/v0.2.md @@ -0,0 +1,50 @@ +--- +title: Announcing Envoy Gateway v0.2 +linktitle: v0.2 +subtitle: Major Update +description: Envoy Gateway v0.2 release announcement. +publishdate: 2022-10-20 +release: v0.2.0 +skip_list: true +aliases: +- /releases/v0.2 +- /releases/v0.2.0 +--- +# Envoy Gateway Release v0.2 + +We are pleased to announce the release of Envoy Gateway v0.2! + +This is the first functional release of Envoy Gateway. We would like to thank the entire Envoy Gateway community for +helping publish the release. + +| [Release Notes][] | [Docs][docs] | [Compatibility Matrix][matrix] | [Download][] | +|-------------------|--------------|--------------------------------|--------------| + +## What's New + +The release adds a ton of features and functionality. Here are some highlights: + +### Kubernetes Support + +Run Envoy Gateway in a Kubernetes cluster. Checkout the [quickstart guide][] to get started with Envoy Gateway in a few +simple steps. + +### Gateway API Support + +Envoy Gateway supports Gateway API resources for running and configuring a managed fleet of Envoy proxies. Envoy Gateway +passes Gateway API core [conformance tests][] and supports GatewayClass, Gateway, HTTPRoute, and TLSRoute resources. See +the [documentation][docs] for additional details on how to use Envoy Gateway for your edge proxy and API gateway needs. + +## Envoy Gateway at EnvoyCon NA + +Envoy Gateway will be at [EnvoyCon NA][] this October in Detroit. Don't miss [our talk][] to learn more about the +release and future direction of the project. + +[Release Notes]: https://github.com/envoyproxy/gateway/blob/main/release-notes/v0.2.0.yaml +[matrix]: https://gateway.envoyproxy.io/intro/compatibility.html +[docs]: https://gateway.envoyproxy.io/index.html +[Download]: https://github.com/envoyproxy/gateway/releases/tag/v0.2.0 +[conformance tests]: https://gateway-api.sigs.k8s.io/concepts/conformance/?h=conformance +[quickstart guide]: https://gateway.envoyproxy.io/user/quickstart.html +[EnvoyCon NA]: https://events.linuxfoundation.org/envoycon-north-america/program/schedule/ +[our talk]: https://sched.co/1AO5S diff --git a/docs/v0.4/releases/v0.3.md b/docs/v0.4/releases/v0.3.md new file mode 100644 index 00000000000..96d6d6d49eb --- /dev/null +++ b/docs/v0.4/releases/v0.3.md @@ -0,0 +1,50 @@ +--- +title: Announcing Envoy Gateway v0.3 +linktitle: v0.3 +subtitle: Major Update +description: Envoy Gateway v0.3 release announcement. +publishdate: 2023-02-09 +release: v0.3.0 +skip_list: true +aliases: +- /releases/v0.3 +- /releases/v0.3.0 +--- +# Envoy Gateway Release v0.3 + +We are pleased to announce the release of Envoy Gateway v0.3! + +This is the second functional release of Envoy Gateway. We would like to thank the entire Envoy Gateway community for +helping publish the release. + +| [Release Notes][] | [Docs][docs] | [Compatibility Matrix][matrix] | [Download][] | +|-------------------|--------------|--------------------------------|--------------| + +## What's New + +The release adds a ton of features and functionality. Here are some highlights: + +### Add Support for extended Gateway API fields + ++ Added Support for HTTPRoute URLRewrite Filter ++ Added Support for HTTPRoute RequestMirror Filter ++ Added Support for HTTPRoute ResponseHeaderModifier Filter + +### Add Support for experimental Gateway APIs + ++ Added Support for the TCPRoute API ++ Added Support for the UDPRoute API ++ Added Support for the GRPCRoute API + +### Add Support for Rate Limiting + ++ Added Support for Global Rate Limiting + +### Add Support for Authentication + ++ Added Support for Request Authentication + +[Release Notes]: https://github.com/envoyproxy/gateway/blob/main/release-notes/v0.3.0.yaml +[matrix]: https://gateway.envoyproxy.io/v0.3.0/intro/compatibility.html +[docs]: https://gateway.envoyproxy.io/v0.3.0/index.html +[Download]: https://github.com/envoyproxy/gateway/releases/tag/v0.3.0 diff --git a/docs/v0.4/releases/v0.4.md b/docs/v0.4/releases/v0.4.md new file mode 100644 index 00000000000..81a9bc3a0ed --- /dev/null +++ b/docs/v0.4/releases/v0.4.md @@ -0,0 +1,61 @@ +--- +title: Announcing Envoy Gateway v0.4 +linktitle: v0.4 +subtitle: Major Update +description: Envoy Gateway v0.4 release announcement. +publishdate: 2023-04-24 +release: v0.4.0 +skip_list: true +aliases: +- /releases/v0.4 +- /releases/v0.4.0 +--- +# Envoy Gateway Release v0.4 + +We are pleased to announce the release of Envoy Gateway v0.4! + +This is the third functional release of Envoy Gateway. We would like to thank the entire Envoy Gateway community for +helping publish the release. + +| [Release Notes][] | [Docs][docs] | [Compatibility Matrix][matrix] | [Download][] | +|-------------------|--------------|--------------------------------|--------------| + +## What's New + +The release adds a ton of features and functionality. Here are some highlights: + +### Upgrade Gateway API Dependency + ++ Upgraded to Gateway API v0.6.2 + +### Add Helm Support + ++ Installation of Envoy Gateway can now be done through helm + +### Add egctl CLI Tool + ++ Added egctl Support for Dry Runs of Gateway API Config ++ Added egctl Support for Dumping Envoy Proxy xDS Resources + +### Add Support for extending Envoy Gateway + ++ Added Initial Framework for Building an Extension on top of Envoy Gateway + +### Ratelimiting + ++ Added Support for Ratelimiting Based On IP Subnet + +### API Updates + ++ Added Support for Custom Envoy Proxy Bootstrap Config ++ Added Support for Configuring the Envoy Proxy Image and Service ++ Added Support for Configuring Annotations, Resources, and Securitycontext Settings on Ratelimit Infra and Envoy Proxy ++ Added Support for Using Multiple Certificates on a Single Fully Qualified Domain Name ++ Envoy Proxy Pod and Container SecurityContext is now Configurable ++ Added Support for Service Method Match in GRPCRoute ++ Added EDS Support + +[Release Notes]: https://github.com/envoyproxy/gateway/blob/main/release-notes/v0.4.0.yaml +[matrix]: https://gateway.envoyproxy.io/v0.4.0/intro/compatibility.html +[docs]: https://gateway.envoyproxy.io/v0.4.0/index.html +[Download]: https://github.com/envoyproxy/gateway/releases/tag/v0.4.0 diff --git a/docs/v0.4/roadmap.rst b/docs/v0.4/roadmap.rst new file mode 100644 index 00000000000..711b6245503 --- /dev/null +++ b/docs/v0.4/roadmap.rst @@ -0,0 +1,9 @@ +Roadmap +======= + +Learn about the future direction of Envoy Gateway. + +.. toctree:: + :maxdepth: 2 + + design/roadmap diff --git a/docs/v0.4/user/authn.md b/docs/v0.4/user/authn.md new file mode 100644 index 00000000000..2d65228c814 --- /dev/null +++ b/docs/v0.4/user/authn.md @@ -0,0 +1,94 @@ +# Request Authentication + +This guide provides instructions for configuring [JSON Web Token (JWT)][jwt] authentication. JWT authentication checks +if an incoming request has a valid JWT before routing the request to a backend service. Currently, Envoy Gateway only +supports validating a JWT from an HTTP header, e.g. `Authorization: Bearer `. + +## Installation + +Follow the steps from the [Quickstart](quickstart.md) guide to install Envoy Gateway and the example manifest. +Before proceeding, you should be able to query the example backend using HTTP. + +## Configuration + +Allow requests with a valid JWT by creating an [AuthenticationFilter][] and referencing it from the example HTTPRoute. + +```shell +kubectl apply -f https://raw.githubusercontent.com/envoyproxy/gateway/v0.4/examples/kubernetes/authn/jwt.yaml +``` + +The HTTPRoute is now updated to authenticate requests for `/foo` and allow unauthenticated requests to `/bar`. The +`/foo` route rule references an AuthenticationFilter that provides the JWT authentication configuration. + +Verify the HTTPRoute configuration and status: + +```shell +kubectl get httproute/backend -o yaml +``` + +The AuthenticationFilter is configured for JWT authentication and uses a single [JSON Web Key Set (JWKS)][jwks] +provider for authenticating the JWT. + +Verify the AuthenticationFilter configuration: + +```shell +kubectl get authenticationfilter/jwt-example -o yaml +``` + +## Testing + +Ensure the `GATEWAY_HOST` environment variable from the [Quickstart](quickstart.md) guide is set. If not, follow the +Quickstart instructions to set the variable. + +```shell +echo $GATEWAY_HOST +``` + +Verify that requests to `/foo` are denied without a JWT: + +```shell +curl -sS -o /dev/null -H "Host: www.example.com" -w "%{http_code}\n" http://$GATEWAY_HOST/foo +``` + +A `401` HTTP response code should be returned. + +Get the JWT used for testing request authentication: + +```shell +TOKEN=$(curl https://raw.githubusercontent.com/envoyproxy/gateway/main/examples/kubernetes/authn/test.jwt -s) && echo "$TOKEN" | cut -d '.' -f2 - | base64 --decode - +``` + +__Note:__ The above command decodes and returns the token's payload. You can replace `f2` with `f1` to view the token's +header. + +Verify that a request to `/foo` with a valid JWT is allowed: + +```shell +curl -sS -o /dev/null -H "Host: www.example.com" -H "Authorization: Bearer $TOKEN" -w "%{http_code}\n" http://$GATEWAY_HOST/foo +``` + +A `200` HTTP response code should be returned. + +Verify that requests to `/bar` are allowed __without__ a JWT: + +```shell +curl -sS -o /dev/null -H "Host: www.example.com" -w "%{http_code}\n" http://$GATEWAY_HOST/bar +``` + +## Clean-Up + +Follow the steps from the [Quickstart](quickstart.md) guide to uninstall Envoy Gateway and the example manifest. + +Delete the AuthenticationFilter: + +```shell +kubectl delete authenticationfilter/jwt-example +``` + +## Next Steps + +Checkout the [Developer Guide](../dev/README.md) to get involved in the project. + +[jwt]: https://tools.ietf.org/html/rfc7519 +[AuthenticationFilter]: https://gateway.envoyproxy.io/v0.4/api/extension_types.html#authenticationfilter +[jwks]: https://tools.ietf.org/html/rfc7517 diff --git a/docs/v0.4/user/customize-envoyproxy.md b/docs/v0.4/user/customize-envoyproxy.md new file mode 100644 index 00000000000..a57449bb113 --- /dev/null +++ b/docs/v0.4/user/customize-envoyproxy.md @@ -0,0 +1,252 @@ +# Customize EnvoyProxy + +Envoy Gateway provides a [EnvoyProxy][] CRD that can be linked to the ParametersRef +in GatewayClass y cluster admins to customize the managed EnvoyProxy Deployment and +Service. To learn more about GatewayClass and ParametersRef, please refer to [Gateway API documentation][]. + +## Installation + +Follow the steps from the [Quickstart Guide](quickstart.md) to install Envoy Gateway and the example manifest. +Before proceeding, you should be able to query the example backend using HTTP. + +## Add GatewayClass ParametersRef + +First, you need to add ParametersRef in GatewayClass, and refer to EnvoyProxy Config: + +```shell +cat < GET /get HTTP/1.1 +> Host: www.marketing.example.com +> User-Agent: curl/7.86.0 +> Accept: */* +> +Handling connection for 8888 +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< content-type: application/json +< x-content-type-options: nosniff +< date: Thu, 20 Apr 2023 19:19:42 GMT +< content-length: 521 +< x-envoy-upstream-service-time: 0 +< server: envoy +< +{ + "path": "/get", + "host": "www.marketing.example.com", + "method": "GET", + "proto": "HTTP/1.1", + "headers": { + "Accept": [ + "*/*" + ], + "User-Agent": [ + "curl/7.86.0" + ], + "X-Envoy-Expected-Rq-Timeout-Ms": [ + "15000" + ], + "X-Envoy-Internal": [ + "true" + ], + "X-Forwarded-For": [ + "10.1.0.157" + ], + "X-Forwarded-Proto": [ + "http" + ], + "X-Request-Id": [ + "c637977c-458a-48ae-92b3-f8c429849322" + ] + }, + "namespace": "marketing", + "ingress": "", + "service": "", + "pod": "backend-74888f465f-bcs8f" +* Connection #0 to host localhost left intact +``` + +* Lets deploy Envoy Gateway in the `product` namespace + +``` +helm install --set config.envoyGateway.gateway.controllerName=gateway.envoyproxy.io/product-gatewayclass-controller eg-product oci://docker.io/envoyproxy/gateway-helm --version v0.4 -n product --create-namespace +``` + +Lets create a `GatewayClass` linked to the product team's Envoy Gateway controller, and as well other resources linked to it, so the `backend` application operated by this team can be exposed to external clients. + +```shell +cat < GET /get HTTP/1.1 +> Host: www.product.example.com +> User-Agent: curl/7.86.0 +> Accept: */* +> +Handling connection for 8889 +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< content-type: application/json +< x-content-type-options: nosniff +< date: Thu, 20 Apr 2023 19:20:17 GMT +< content-length: 517 +< x-envoy-upstream-service-time: 0 +< server: envoy +< +{ + "path": "/get", + "host": "www.product.example.com", + "method": "GET", + "proto": "HTTP/1.1", + "headers": { + "Accept": [ + "*/*" + ], + "User-Agent": [ + "curl/7.86.0" + ], + "X-Envoy-Expected-Rq-Timeout-Ms": [ + "15000" + ], + "X-Envoy-Internal": [ + "true" + ], + "X-Forwarded-For": [ + "10.1.0.156" + ], + "X-Forwarded-Proto": [ + "http" + ], + "X-Request-Id": [ + "39196453-2250-4331-b756-54003b2853c2" + ] + }, + "namespace": "product", + "ingress": "", + "service": "", + "pod": "backend-74888f465f-64fjs" +* Connection #0 to host localhost left intact +``` + +With the below command you can ensure that you are no able to access the marketing team's backend exposed using the `www.marketing.example.com` hostname +and the product team's data plane. + +```shell +curl --verbose --header "Host: www.marketing.example.com" http://localhost:8889/get +``` + +``` +* Trying 127.0.0.1:8889... +* Connected to localhost (127.0.0.1) port 8889 (#0) +> GET /get HTTP/1.1 +> Host: www.marketing.example.com +> User-Agent: curl/7.86.0 +> Accept: */* +> +Handling connection for 8889 +* Mark bundle as not supporting multiuse +< HTTP/1.1 404 Not Found +< date: Thu, 20 Apr 2023 19:22:13 GMT +< server: envoy +< content-length: 0 +< +* Connection #0 to host localhost left intact +``` diff --git a/docs/v0.4/user/egctl.md b/docs/v0.4/user/egctl.md new file mode 100644 index 00000000000..4a657dd5af7 --- /dev/null +++ b/docs/v0.4/user/egctl.md @@ -0,0 +1,804 @@ +# egctl + +`egctl` is a command line tool to provide additional functionality for Envoy Gateway users. + +## Installing egctl + +This guide shows how to install the egctl CLI. egctl can be installed either from source, or from pre-built binary releases. + +### From The Envoy Gateway Project + +The Envoy Gateway project provides two ways to fetch and install egctl. These are the official methods to get egctl releases. Installation through those methods can be found below the official methods. + +### From the Binary Releases + +Every [release](https://github.com/envoyproxy/gateway/releases) of egctl provides binary releases for a variety of OSes. These binary versions can be manually downloaded and installed. + +1. Download your [desired version](https://github.com/envoyproxy/gateway/releases) +2. Unpack it (tar -zxvf egctl_v0.4_linux_amd64.tar.gz) +3. Find the egctl binary in the unpacked directory, and move it to its desired destination (mv bin/linux/amd64/egctl /usr/local/bin/egctl) + +From there, you should be able to run: `egctl help`. + +### From Script + +`egctl` now has an installer script that will automatically grab the v0.4 release version of egctl and install it locally. + +You can fetch that script, and then execute it locally. It's well documented so that you can read through it and understand what it is doing before you run it. + +```shell +curl -fsSL -o get-egctl.sh https://gateway.envoyproxy.io/get-egctl.sh + +chmod +x get-egctl.sh + +# get help info of the +bash get-egctl.sh --help + +# install the v0.4 development version of egctl +bash VERSION=v0.4 get-egctl.sh +``` + +Yes, you can just use the below command if you want to live on the edge. + +```shell +curl https://gateway.envoyproxy.io/get-egctl.sh | VERSION=v0.4 bash +``` + +## egctl experimental translate + +This subcommand allows users to translate from an input configuration type to an output configuration type. + +In the below example, we will translate the Kubernetes resources (including the Gateway API resources) into xDS +resources. + +```shell +cat < GET /get HTTP/1.1 +> Host: headers.example +> User-Agent: curl/7.81.0 +> Accept: */* +> add-header: something +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< content-type: application/json +< x-content-type-options: nosniff +< content-length: 474 +< x-envoy-upstream-service-time: 0 +< server: envoy +< +... + "headers": { + "Accept": [ + "*/*" + ], + "Add-Header": [ + "something", + "foo" + ], +... +``` + +## Setting Request Headers + +Setting headers is similar to adding headers. If the request does not have the header configured by the filter, then it +will be added, but unlike [adding request headers](#adding-request-headers) which will append the value of the header if +the request already contains it, setting a header will cause the value to be replaced by the value configured in the +filter. + +```shell +cat < GET /get HTTP/1.1 +> Host: headers.example +> User-Agent: curl/7.81.0 +> Accept: */* +> add-header: something +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< content-type: application/json +< x-content-type-options: nosniff +< content-length: 474 +< x-envoy-upstream-service-time: 0 +< server: envoy +< + "headers": { + "Accept": [ + "*/*" + ], + "Set-Header": [ + "foo" + ], +... +``` + +## Removing Request Headers + +Headers can be removed from a request by simply supplying a list of header names. + +Setting headers is similar to adding headers. If the request does not have the header configured by the filter, then it +will be added, but unlike [adding request headers](#adding-request-headers) which will append the value of the header if +the request already contains it, setting a header will cause the value to be replaced by the value configured in the +filter. + +```shell +cat < GET /get HTTP/1.1 +> Host: headers.example +> User-Agent: curl/7.81.0 +> Accept: */* +> add-header: something +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< content-type: application/json +< x-content-type-options: nosniff +< content-length: 474 +< x-envoy-upstream-service-time: 0 +< server: envoy +< + + "headers": { + "Accept": [ + "*/*" + ], + "Add-Header": [ + "something" + ], +... +``` + +## Combining Filters + +Headers can be added/set/removed in a single filter on the same HTTPRoute and they will all perform as expected + +```shell +cat < GET /get HTTP/1.1 +> Host: headers.example +> User-Agent: curl/7.81.0 +> Accept: */* +> X-Echo-Set-Header: X-Foo: value1 +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< content-type: application/json +< x-content-type-options: nosniff +< content-length: 474 +< x-envoy-upstream-service-time: 0 +< server: envoy +< x-foo: value1 +< add-header: foo +< +... + "headers": { + "Accept": [ + "*/*" + ], + "X-Echo-Set-Header": [ + "X-Foo: value1" + ] +... +``` + +## Setting Response Headers + +Setting headers is similar to adding headers. If the response does not have the header configured by the filter, then it +will be added, but unlike [adding response headers](#adding-response-headers) which will append the value of the header +if the response already contains it, setting a header will cause the value to be replaced by the value configured in the +filter. + +```shell +cat < GET /get HTTP/1.1 +> Host: headers.example +> User-Agent: curl/7.81.0 +> Accept: */* +> X-Echo-Set-Header: set-header: value1 +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< content-type: application/json +< x-content-type-options: nosniff +< content-length: 474 +< x-envoy-upstream-service-time: 0 +< server: envoy +< set-header: foo +< + "headers": { + "Accept": [ + "*/*" + ], + "X-Echo-Set-Header": [ + "set-header": value1" + ] +... +``` + +## Removing Response Headers + +Headers can be removed from a response by simply supplying a list of header names. + +Setting headers is similar to adding headers. If the response does not have the header configured by the filter, then it +will be added, but unlike [adding response headers](#adding-response-headers) which will append the value of the header +if the response already contains it, setting a header will cause the value to be replaced by the value configured in the +filter. + +```shell +cat < GET /get HTTP/1.1 +> Host: headers.example +> User-Agent: curl/7.81.0 +> Accept: */* +> X-Echo-Set-Header: remove-header: value1 +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< content-type: application/json +< x-content-type-options: nosniff +< content-length: 474 +< x-envoy-upstream-service-time: 0 +< server: envoy +< + + "headers": { + "Accept": [ + "*/*" + ], + "X-Echo-Set-Header": [ + "remove-header": value1" + ] +... +``` + +## Combining Filters + +Headers can be added/set/removed in a single filter on the same HTTPRoute and they will all perform as expected + +```shell +cat < GET /get HTTP/1.1 +> Host: backends.example +> User-Agent: curl/7.81.0 +> Accept: */* +> add-header: something +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< content-type: application/json +< x-content-type-options: nosniff +< content-length: 474 +< x-envoy-upstream-service-time: 0 +< server: envoy +< +... + "namespace": "default", + "ingress": "", + "service": "", + "pod": "backend-79665566f5-s589f" +... +``` + +## Multiple backendRefs + +If multiple backendRefs are configured, then traffic will be split between the backendRefs equally unless a weight is +configured. + +First, create a second instance of the example app from the quickstart: + +```shell +cat < GET /get HTTP/1.1 +> Host: backends.example +> User-Agent: curl/7.81.0 +> Accept: */* +> add-header: something +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< content-type: application/json +< x-content-type-options: nosniff +< content-length: 474 +< x-envoy-upstream-service-time: 0 +< server: envoy +< +... + "namespace": "default", + "ingress": "", + "service": "", + "pod": "backend-75bcd4c969-lsxpz" +... +``` + +## Weighted backendRefs + +If multiple backendRefs are configured and an un-even traffic split between the backends is desired, then the `weight` +field can be used to control the weight of requests to each backend. If weight is not configured for a backendRef it is +assumed to be `1`. + +The [weight field in a backendRef][backendRefs] controls the distribution of the traffic split. The proportion of +requests to a single backendRef is calculated by dividing its `weight` by the sum of all backendRef weights in the +HTTPRoute. The weight is not a percentage and the sum of all weights does not need to add up to 100. + +The HTTPRoute below will configure the gateway to send 80% of the traffic to the backend service, and 20% to the +backend-2 service. + +```shell +cat < GET /get HTTP/1.1 +> Host: backends.example +> User-Agent: curl/7.81.0 +> Accept: */* +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 500 Internal Server Error +< server: envoy +< content-length: 0 +< +``` + +[HTTPRoute]: https://gateway-api.sigs.k8s.io/api-types/httproute/ +[backendRefs]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.BackendRef diff --git a/docs/v0.4/user/http-urlrewrite.md b/docs/v0.4/user/http-urlrewrite.md new file mode 100644 index 00000000000..88e29c3269c --- /dev/null +++ b/docs/v0.4/user/http-urlrewrite.md @@ -0,0 +1,295 @@ +# HTTP URL Rewrite + +[HTTPURLRewriteFilter][] defines a filter that modifies a request during forwarding. At most one of these filters may be +used on a Route rule. This MUST NOT be used on the same Route rule as a HTTPRequestRedirect filter. + +## Prerequisites + +Follow the steps from the [Quickstart Guide](quickstart.md) to install Envoy Gateway and the example manifest. +Before proceeding, you should be able to query the example backend using HTTP. + +## Rewrite URL Prefix Path + +You can configure to rewrite the prefix in the url like below. In this example, any curls to +`http://${GATEWAY_HOST}/get/xxx` will be rewritten to `http://${GATEWAY_HOST}/replace/xxx`. + +```shell +cat < GET /get/origin/path HTTP/1.1 +> Host: path.rewrite.example +> User-Agent: curl/7.85.0 +> Accept: */* +> + +< HTTP/1.1 200 OK +< content-type: application/json +< x-content-type-options: nosniff +< date: Wed, 21 Dec 2022 11:03:28 GMT +< content-length: 503 +< x-envoy-upstream-service-time: 0 +< server: envoy +< +{ + "path": "/replace/origin/path", + "host": "path.rewrite.example", + "method": "GET", + "proto": "HTTP/1.1", + "headers": { + "Accept": [ + "*/*" + ], + "User-Agent": [ + "curl/7.85.0" + ], + "X-Envoy-Expected-Rq-Timeout-Ms": [ + "15000" + ], + "X-Envoy-Original-Path": [ + "/get/origin/path" + ], + "X-Forwarded-Proto": [ + "http" + ], + "X-Request-Id": [ + "fd84b842-9937-4fb5-83c7-61470d854b90" + ] + }, + "namespace": "default", + "ingress": "", + "service": "", + "pod": "backend-6fdd4b9bd8-8vlc5" +... +``` + +You can see that the `X-Envoy-Original-Path` is `/get/origin/path`, but the actual path is `/replace/origin/path`. + +## Rewrite URL Full Path + +You can configure to rewrite the fullpath in the url like below. In this example, any request sent to +`http://${GATEWAY_HOST}/get/origin/path/xxxx` will be rewritten to +`http://${GATEWAY_HOST}/force/replace/fullpath`. + +```shell +cat < GET /get/origin/path/extra HTTP/1.1 +> Host: path.rewrite.example +> User-Agent: curl/7.85.0 +> Accept: */* +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< content-type: application/json +< x-content-type-options: nosniff +< date: Wed, 21 Dec 2022 11:09:31 GMT +< content-length: 512 +< x-envoy-upstream-service-time: 0 +< server: envoy +< +{ + "path": "/force/replace/fullpath", + "host": "path.rewrite.example", + "method": "GET", + "proto": "HTTP/1.1", + "headers": { + "Accept": [ + "*/*" + ], + "User-Agent": [ + "curl/7.85.0" + ], + "X-Envoy-Expected-Rq-Timeout-Ms": [ + "15000" + ], + "X-Envoy-Original-Path": [ + "/get/origin/path/extra" + ], + "X-Forwarded-Proto": [ + "http" + ], + "X-Request-Id": [ + "8ab774d6-9ffa-4faa-abbb-f45b0db00895" + ] + }, + "namespace": "default", + "ingress": "", + "service": "", + "pod": "backend-6fdd4b9bd8-8vlc5" +... +``` + +You can see that the `X-Envoy-Original-Path` is `/get/origin/path/extra`, but the actual path is +`/force/replace/fullpath`. + +## Rewrite Host Name + +You can configure to rewrite the hostname like below. In this example, any requests sent to +`http://${GATEWAY_HOST}/get` with `--header "Host: path.rewrite.example"` will rewrite host into `envoygateway.io`. + +```shell +cat < GET /get HTTP/1.1 +> Host: path.rewrite.example +> User-Agent: curl/7.85.0 +> Accept: */* +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< content-type: application/json +< x-content-type-options: nosniff +< date: Wed, 21 Dec 2022 11:15:15 GMT +< content-length: 481 +< x-envoy-upstream-service-time: 0 +< server: envoy +< +{ + "path": "/get", + "host": "envoygateway.io", + "method": "GET", + "proto": "HTTP/1.1", + "headers": { + "Accept": [ + "*/*" + ], + "User-Agent": [ + "curl/7.85.0" + ], + "X-Envoy-Expected-Rq-Timeout-Ms": [ + "15000" + ], + "X-Forwarded-Host": [ + "path.rewrite.example" + ], + "X-Forwarded-Proto": [ + "http" + ], + "X-Request-Id": [ + "39aa447c-97b9-45a3-a675-9fb266ab1af0" + ] + }, + "namespace": "default", + "ingress": "", + "service": "", + "pod": "backend-6fdd4b9bd8-8vlc5" +... +``` + +You can see that the `X-Forwarded-Host` is `path.rewrite.example`, but the actual host is `envoygateway.io`. + +[HTTPURLRewriteFilter]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPURLRewriteFilter diff --git a/docs/v0.4/user/quickstart.md b/docs/v0.4/user/quickstart.md new file mode 100644 index 00000000000..e53b44c8219 --- /dev/null +++ b/docs/v0.4/user/quickstart.md @@ -0,0 +1,97 @@ +# Quickstart + +This guide will help you get started with Envoy Gateway in a few simple steps. + +## Prerequisites + +A Kubernetes cluster. + +__Note:__ Refer to the [Compatibility Matrix](../intro/compatibility.rst) for supported Kubernetes versions. + +## Installation + +Install the Gateway API CRDs and Envoy Gateway: + +```shell +helm install eg oci://docker.io/envoyproxy/gateway-helm --version v0.4 -n envoy-gateway-system --create-namespace +``` + +Wait for Envoy Gateway to become available: + +```shell +kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available +``` + +Install the GatewayClass, Gateway, HTTPRoute and example app: + +```shell +kubectl apply -f https://github.com/envoyproxy/gateway/releases/download/v0.4/quickstart.yaml -n default +``` + +**Note**: [`quickstart.yaml`] defines that Envoy Gateway will listen for +traffic on port 80 on its globally-routable IP address, to make it easy to use +browsers to test Envoy Gateway. When Envoy Gateway sees that its Listener is +using a privileged port (<1024), it will map this internally to an +unprivileged port, so that Envoy Gateway doesn't need additional privileges. +It's important to be aware of this mapping, since you may need to take it into +consideration when debugging. + +[`quickstart.yaml`]: https://github.com/envoyproxy/gateway/releases/download/v0.4/quickstart.yaml + +## Testing the Configuration + +Get the name of the Envoy service created the by the example Gateway: + +```shell +export ENVOY_SERVICE=$(kubectl get svc -n envoy-gateway-system --selector=gateway.envoyproxy.io/owning-gateway-namespace=default,gateway.envoyproxy.io/owning-gateway-name=eg -o jsonpath='{.items[0].metadata.name}') +``` + +Port forward to the Envoy service: + +```shell +kubectl -n envoy-gateway-system port-forward service/${ENVOY_SERVICE} 8888:80 & +``` + +Curl the example app through Envoy proxy: + +```shell +curl --verbose --header "Host: www.example.com" http://localhost:8888/get +``` + +### External LoadBalancer Support + +You can also test the same functionality by sending traffic to the External IP. To get the external IP of the +Envoy service, run: + +```shell +export GATEWAY_HOST=$(kubectl get svc/${ENVOY_SERVICE} -n envoy-gateway-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}') +``` + +In certain environments, the load balancer may be exposed using a hostname, instead of an IP address. If so, replace +`ip` in the above command with `hostname`. + +Curl the example app through Envoy proxy: + +```shell +curl --verbose --header "Host: www.example.com" http://$GATEWAY_HOST/get +``` + +## Clean-Up + +Use the steps in this section to uninstall everything from the quickstart guide. + +Delete the GatewayClass, Gateway, HTTPRoute and Example App: + +```shell +kubectl delete -f https://github.com/envoyproxy/gateway/releases/download/v0.4/quickstart.yaml --ignore-not-found=true +``` + +Delete the Gateway API CRDs and Envoy Gateway: + +```shell +helm uninstall eg -n envoy-gateway-system +``` + +## Next Steps + +Checkout the [Developer Guide](../dev/README.md) to get involved in the project. diff --git a/docs/v0.4/user/rate-limit.md b/docs/v0.4/user/rate-limit.md new file mode 100644 index 00000000000..4ddc3292e4e --- /dev/null +++ b/docs/v0.4/user/rate-limit.md @@ -0,0 +1,631 @@ +# Rate limit + +Rate limit is a feature that allows the user to limit the number of incoming requests to a predefined value based on attributes within the traffic flow. + +Here are some reasons why you may want to implements Rate limits + +* To prevent malicious activity such as DDoS attacks. +* To prevent applications and its resources (such as a database) from getting overloaded. +* To create API limits based on user entitlements. + +Envoy Gateway supports [Global rate limiting][], where the rate limit is common across all the instances of Envoy proxies where its applied +i.e. if the data plane has 2 replicas of Envoy running, and the rate limit is 10 requests/second, this limit is common and will be hit +if 5 requests pass through the first replica and 5 requests pass through the second replica within the same second. + +Envoy Gateway introduces a new CRD called [RateLimitFilter][] that allows the user to describe their rate limit intent. This instantiated resource +can be linked to a [HTTPRoute][] resource using an [ExtensionRef][] filter. + +## Prerequisites + +### Install Envoy Gateway + +* Follow the steps from the [Quickstart Guide](quickstart.md) to install Envoy Gateway and the example manifest. +Before proceeding, you should be able to query the example backend using HTTP. + +### Install Redis + +* The global rate limit feature is based on [Envoy Ratelimit][] which requires a Redis instance as its caching layer. +Lets install a Redis deployment in the `redis-system` namespce. + +```shell +cat <> DiG 9.18.1-1ubuntu1.1-Ubuntu <<>> @49.51.177.138 -p 5300 foo.bar.com +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58125 +;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 3 +;; WARNING: recursion requested but not available + +;; OPT PSEUDOSECTION: +; EDNS: version: 0, flags:; udp: 1232 +; COOKIE: 24fb86eba96ebf62 (echoed) +;; QUESTION SECTION: +;foo.bar.com. IN A + +;; ADDITIONAL SECTION: +foo.bar.com. 0 IN A 10.244.0.19 +_udp.foo.bar.com. 0 IN SRV 0 0 42376 . + +;; Query time: 1 msec +;; SERVER: 49.51.177.138#5300(49.51.177.138) (UDP) +;; WHEN: Fri Jan 13 10:20:34 UTC 2023 +;; MSG SIZE rcvd: 114 +``` + +## Clean-Up + +Follow the steps from the [Quickstart Guide](quickstart.md) to uninstall Envoy Gateway. + +Delete the CoreDNS example manifest and the UDPRoute: + +```shell +kubectl delete deploy/coredns +kubectl delete service/coredns +kubectl delete cm/coredns +kubectl delete udproute/coredns +``` + +## Next Steps + +Checkout the [Developer Guide](../dev/README.md) to get involved in the project. + +[UDPRoute]: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.UDPRoute +[UDP proxy documentation]: https://www.envoyproxy.io/docs/envoy/v0.4/configuration/listeners/udp_filters/udp_proxy diff --git a/docs/v0.4/user_docs.rst b/docs/v0.4/user_docs.rst new file mode 100644 index 00000000000..3ddca61b97a --- /dev/null +++ b/docs/v0.4/user_docs.rst @@ -0,0 +1,25 @@ +User Guides +=========== + +Learn how to deploy, use, and operate Envoy Gateway. + +.. toctree:: + :maxdepth: 1 + + user/quickstart + user/http-routing + user/http-redirect + user/http-urlrewrite + user/http-traffic-splitting + user/http-request-headers + user/http-response-headers + user/secure-gateways + user/tls-passthrough + user/tcp-routing + user/udp-routing + user/grpc-routing + user/authn + user/rate-limit + user/egctl + user/customize-envoyproxy + user/deployment-mode diff --git a/release-notes/v0.4.0.yaml b/release-notes/v0.4.0.yaml new file mode 100644 index 00000000000..a4938621f97 --- /dev/null +++ b/release-notes/v0.4.0.yaml @@ -0,0 +1,64 @@ +date: April 24, 2023 + +changes: + - area: documentation + change: | + Added Docs for Installing and Using egctl + + - area: installation + change: | + Added Helm Installation Support + Added Support for Ratelimiting Based On IP Subnet + Added Gateway API Support Doc + Added Namespace Resource to Helm Templates + Updated Installation Yaml to Use the envoy-gateway-system Namespace + + - area: api + change: | + Upgraded to Gateway API v0.6.2 + Added Support for Custom Envoy Proxy Bootstrap Config + Added Support for Configuring the Envoy Proxy Image and Service + Added Support for Configuring Annotations, Resources, and Securitycontext Settings on Ratelimit Infra and Envoy Proxy + Added Support for Using Multiple Certificates on a Single Fully Qualified Domain Name + Gateway Status Address is now Populated for ClusterIP type Envoy Services + Envoy Proxy Pod and Container SecurityContext is now Configurable + Added Custom Envoy Gateway Extensions Framework + Added Support for Service Method Match in GRPCRoute + Fixed a Bug in the Extension Hooks for xDS Virtual Hosts and Routes + + - area: ci-tooling-testing + change: | + Fixed CI Flakes During Helm Install + Added Test To Ensure Static xDS Cluster Has Same Field Values as Dynamic Cluster + Added egctl to Build and Test CI Workflow + Code Coverage Thresholds are now Enforced by CI + Fixed latest-release-check CI Job Failures + Added Auto Release Tooling for Charts + + - area: conformance + change: | + Enabled GatewayWithAttachedRoutes Test + Enabled Enable HTTPRouteInvalidParentRefNotMatchingSectionName Test + Enabled Enable HTTPRouteDisallowedKind Test + Re-Enabled Gateway/HTTPRouteObservedGenerationBump Test + + - area: translator + change: | + Added Support for Dynamic GatewayControllerName in Route Status + + - area: providers + change: | + Update GatewayClass Status Based on EnvoyProxy Config Validation + + - area: xds + change: | + Added EDS Support + Fixed PathSeparatedPrefix and Optimized Logic for Prefixes Ending With Trailing Slash + Updated Deprecated RegexMatcher + Refactored Authn and Ratelimit Features to Reuse buildXdsCluster + + - area: cli + change: | + Added egctl CLI Tool + Added egctl Support for Dry Runs of Gateway API Config + Added egctl Support for Dumping Envoy Proxy xDS Resources From 4f0103491f7a3797f54747f45ce2c3655d3b416c Mon Sep 17 00:00:00 2001 From: David Boslee Date: Fri, 23 Jun 2023 08:30:04 -0600 Subject: [PATCH 05/47] Add github actions and make teleport workflows (#1) --- .github/workflows/build_and_test.yaml | 15 +----- .github/workflows/cd.yaml | 72 +++++++++++++++++++++++++++ .github/workflows/ci.yaml | 58 +++++++++++++++++++++ .github/workflows/docs.yaml | 12 +---- .github/workflows/latest_release.yaml | 7 +-- .github/workflows/release.yaml | 7 +-- .github/workflows/stale.yml | 5 +- tools/make/common.mk | 1 + tools/make/teleport.mk | 54 ++++++++++++++++++++ 9 files changed, 192 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/cd.yaml create mode 100644 .github/workflows/ci.yaml create mode 100644 tools/make/teleport.mk diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 3ff2b43b34c..fcd44523611 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -1,17 +1,6 @@ name: Build and Test -on: - push: - branches: - - "main" - - "release/v*" - paths-ignore: - - "**/*.png" - pull_request: - branches: - - "main" - - "release/v*" - paths-ignore: - - "**/*.png" +on: workflow_call + jobs: lint: runs-on: ubuntu-latest diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml new file mode 100644 index 00000000000..e263bdf72cd --- /dev/null +++ b/.github/workflows/cd.yaml @@ -0,0 +1,72 @@ +name: cd +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+.*' + +permissions: + contents: read + +jobs: + + push: + name: Build and Push + permissions: + contents: read + checks: read + id-token: write + env: + AWS_REGION: us-west-2 + ECR_AWS_ROLE: arn:aws:iam::146628656107:role/gateway-api-github-action-ecr-role + runs-on: ubuntu-latest + needs: [verify, test] + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-region: ${{ env.AWS_REGION }} + role-to-assume: ${{ env.ECR_AWS_ROLE }} + mask-aws-account-id: 'no' + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Build and push gateway + run: PLATFORM="linux_amd64" make teleport-push + - name: Build and push helm + run: make teleport-helm-push + + verify: + name: Verify + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version-file: go.mod + cache: true + - name: Run Verify + run: make teleport-verify + + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version-file: go.mod + cache: true + - name: Unit Tests + run: make teleport-test diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000000..ce0c58105ef --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,58 @@ +name: ci +on: + pull_request: + types: + - opened + - synchronize + - reopened + - labeled + - unlabeled + +permissions: + contents: read + +jobs: + + build: + name: Build + runs-on: ubuntu-latest + needs: [verify, test] + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Build gateway + run: PLATFORM="linux_amd64" make teleport-build + - name: Build gateway helm + run: CHART_VERSION=v0.0.0-latest TAG=latest make teleport-helm-package + + verify: + name: Verify + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version-file: go.mod + cache: true + - name: Run Verify + run: make teleport-verify + + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version-file: go.mod + cache: true + - name: Unit Tests + run: make teleport-test diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 8b54b48294b..25c5b7880a9 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -1,15 +1,5 @@ name: Docs -on: - push: - branches: - - "main" - paths-ignore: - - "**/*.png" - pull_request: - branches: - - "main" - paths-ignore: - - "**/*.png" +on: workflow_call jobs: docs-lint: diff --git a/.github/workflows/latest_release.yaml b/.github/workflows/latest_release.yaml index cefd48e7632..d335fb406f0 100644 --- a/.github/workflows/latest_release.yaml +++ b/.github/workflows/latest_release.yaml @@ -1,11 +1,6 @@ name: Latest Release -on: - push: - branches: - - "main" - paths-ignore: - - "**/*.png" +on: workflow_call jobs: latest-release: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ec218df6205..68723bf35c5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,10 +1,7 @@ name: Release -on: - push: - # Sequence of patterns matched against refs/tags - tags: - - "v*.*.*" +on: workflow_call + jobs: release: runs-on: ubuntu-latest diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3d96d5ab95d..fced439e18b 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,7 +1,4 @@ -on: - workflow_dispatch: - schedule: - - cron: '0 */4 * * *' +on: workflow_call jobs: prune_stale: diff --git a/tools/make/common.mk b/tools/make/common.mk index 4bda0b272d7..0dbdcd172f8 100644 --- a/tools/make/common.mk +++ b/tools/make/common.mk @@ -19,6 +19,7 @@ SHELL:=/bin/bash # ==================================================================================================== # ROOT Options: # ==================================================================================================== +include tools/make/teleport.mk ROOT_PACKAGE=github.com/envoyproxy/gateway diff --git a/tools/make/teleport.mk b/tools/make/teleport.mk new file mode 100644 index 00000000000..651747ac8d5 --- /dev/null +++ b/tools/make/teleport.mk @@ -0,0 +1,54 @@ +# Teleport specific overrides for building and publishing off of our gateway-api +# and envoy gateway forks. + +# TODO(david): gateway-api use tag reference in release url. +GATEWAY_API_VERSION ?= "v0.6.2" +GATEWAY_RELEASE_URL ?= https://raw.githubusercontent.com/gravitational/gateway-api/teleport/release/experimental-install.yaml + +TAG ?= $(shell git describe --tags --dirty --always) +RELEASE_VERSION ?= ${TAG} +CHART_VERSION ?= ${RELEASE_VERSION} + +REGISTRY ?= public.ecr.aws/gravitational +IMAGE_NAME ?= envoy-gateway +IMAGE ?= ${REGISTRY}/${IMAGE_NAME} +OCI_REGISTRY ?= oci://public.ecr.aws/gravitational + +.PHONY: teleport-generate +teleport-generate: generate manifests + +.PHONY: teleport-test +teleport-test: ## Run tests using teleport specific configuration +teleport-test: test + +.PHONY: teleport-verify +teleport-verify: ## Run lint and gen-check. +teleport-verify: gen-check lint + +teleport-go-build: go.build + +.PHONY: teleport-build +teleport-build: ## Run build using teleport specific configuration. +teleport-build: teleport-go-build image.build + +.PHONY: teleport-push +teleport-push: ## Push the current build of envoy/gateway to teleport's registry. +teleport-push: teleport-build push + +.PHONY: teleport-build-multiarch +teleport-build-multiarch: go.build.multiarch image-multiarch + +teleport-helm-%: RELEASE_VERSION + +.PHONY: teleport-helm-package +teleport-helm-package: ## Package envoy gateway helm chart with teleprot specific overrides. +teleport-helm-package: helm-package + +.PHONY: teleport-helm-push +teleport-helm-push: ## Push envoy gateway helm chart to teleport's OCI registry. +teleport-helm-push: teleport-helm-package helm-push + +.PHONY: teleport-dev-install +teleport-dev-install: ## Install the local changes to gateway the regional kind cluster. +teleport-dev-install: export CLUSTER_NAME := regional +teleport-dev-install: kube-install-image \ No newline at end of file From e044de79eb110d6a5dec48d3f7f93b94e36875a2 Mon Sep 17 00:00:00 2001 From: David Boslee Date: Wed, 5 Jul 2023 13:38:13 -0600 Subject: [PATCH 06/47] Fix helm versioning (#2) --- tools/make/common.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/make/common.mk b/tools/make/common.mk index 0dbdcd172f8..b477dae460c 100644 --- a/tools/make/common.mk +++ b/tools/make/common.mk @@ -23,7 +23,7 @@ include tools/make/teleport.mk ROOT_PACKAGE=github.com/envoyproxy/gateway -RELEASE_VERSION=$(shell cat VERSION) +RELEASE_VERSION?=$(shell cat VERSION) # Set Root Directory Path ifeq ($(origin ROOT_DIR),undefined) From 4f4bffa58f96776bd0a574fd1767ac78dca04081 Mon Sep 17 00:00:00 2001 From: David Boslee Date: Fri, 7 Jul 2023 09:47:21 -0600 Subject: [PATCH 07/47] Differentiate between tcp listener and tcp route names (#6) Previously the name of the first tcp or tls route to be processed was used as the listener name. This lead to envoy draining when the tcp listener name changed which was caused by routes being added/removed or routes being processed in a different order. --- .../out/from-gateway-api-to-xds.all.json | 4 ++-- .../out/from-gateway-api-to-xds.all.yaml | 4 ++-- .../out/from-gateway-api-to-xds.listener.yaml | 4 ++-- internal/gatewayapi/helpers.go | 8 ++++++-- internal/gatewayapi/route.go | 10 ++++++---- ...er-with-tls-terminate-and-passthrough.out.yaml | 3 ++- ...ngle-listener-with-multiple-tcproutes.out.yaml | 3 ++- ...y-with-two-listeners-on-same-tcp-port.out.yaml | 3 ++- ...ners-with-same-port-http-tcp-protocol.out.yaml | 3 ++- ...eners-with-tcproutes-with-sectionname.out.yaml | 6 ++++-- ...rs-with-tcproutes-without-sectionname.out.yaml | 6 ++++-- .../tlsroute-attaching-to-gateway.out.yaml | 3 ++- .../testdata/tlsroute-multiple.out.yaml | 6 ++++-- ...n-other-namespace-allowed-by-refgrant.out.yaml | 3 ++- .../tlsroute-with-empty-hostname.out.yaml | 3 ++- ...tlsroute-with-empty-listener-hostname.out.yaml | 3 ++- internal/ir/xds.go | 14 ++++++++++---- internal/ir/xds_test.go | 12 +++++++++--- .../in/xds-ir/multiple-listeners-same-port.yaml | 6 ++++-- .../multiple-simple-tcp-route-same-port.yaml | 15 ++++++++++----- .../testdata/in/xds-ir/tcp-route-complex.yaml | 3 ++- .../testdata/in/xds-ir/tcp-route-simple.yaml | 3 ++- .../in/xds-ir/tcp-route-weighted-backend.yaml | 3 ++- .../testdata/in/xds-ir/tls-route-passthrough.yaml | 3 ++- ...iple-simple-tcp-route-same-port.listeners.yaml | 2 +- .../out/xds-ir/tcp-route-complex.listeners.yaml | 2 +- .../out/xds-ir/tcp-route-simple.listeners.yaml | 2 +- .../tcp-route-weighted-backend.listeners.yaml | 2 +- .../xds-ir/tls-route-passthrough.listeners.yaml | 2 +- internal/xds/translator/translator.go | 6 +++--- 30 files changed, 95 insertions(+), 52 deletions(-) diff --git a/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.json b/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.json index 771003c8aed..39deac1b1f2 100644 --- a/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.json +++ b/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.json @@ -479,7 +479,7 @@ "@type": "type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector" } }], - "name": "default-eg-tls-passthrough-backend" + "name": "default-eg-tls-passthrough" } } }, { @@ -521,7 +521,7 @@ } }] }], - "name": "default-eg-tcp-backend" + "name": "default-eg-tcp" } } }, { diff --git a/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.yaml b/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.yaml index 445f0e5df70..5ec3461b8a7 100644 --- a/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.yaml +++ b/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.yaml @@ -326,7 +326,7 @@ xds: - name: envoy.filters.listener.tls_inspector typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector - name: default-eg-tls-passthrough-backend + name: default-eg-tls-passthrough - activeState: listener: '@type': type.googleapis.com/envoy.config.listener.v3.Listener @@ -355,7 +355,7 @@ xds: path: /dev/stdout cluster: default-eg-tcp-backend statPrefix: tcp - name: default-eg-tcp-backend + name: default-eg-tcp - activeState: listener: '@type': type.googleapis.com/envoy.config.listener.v3.Listener diff --git a/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.listener.yaml b/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.listener.yaml index 91c3e48b410..97038037fe6 100644 --- a/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.listener.yaml +++ b/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.listener.yaml @@ -119,7 +119,7 @@ xds: - name: envoy.filters.listener.tls_inspector typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector - name: default-eg-tls-passthrough-backend + name: default-eg-tls-passthrough - activeState: listener: '@type': type.googleapis.com/envoy.config.listener.v3.Listener @@ -148,7 +148,7 @@ xds: path: /dev/stdout cluster: default-eg-tcp-backend statPrefix: tcp - name: default-eg-tcp-backend + name: default-eg-tcp - activeState: listener: '@type': type.googleapis.com/envoy.config.listener.v3.Listener diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go index da7b085bd79..32fbb8e2a3d 100644 --- a/internal/gatewayapi/helpers.go +++ b/internal/gatewayapi/helpers.go @@ -393,11 +393,15 @@ func irHTTPListenerName(listener *ListenerContext) string { return fmt.Sprintf("%s-%s-%s", listener.gateway.Namespace, listener.gateway.Name, listener.Name) } -func irTLSListenerName(listener *ListenerContext, tlsRoute *TLSRouteContext) string { +func irTLSRouteName(listener *ListenerContext, tlsRoute *TLSRouteContext) string { return fmt.Sprintf("%s-%s-%s-%s", listener.gateway.Namespace, listener.gateway.Name, listener.Name, tlsRoute.Name) } -func irTCPListenerName(listener *ListenerContext, tcpRoute *TCPRouteContext) string { +func irTCPListenerName(listener *ListenerContext) string { + return fmt.Sprintf("%s-%s-%s", listener.gateway.Namespace, listener.gateway.Name, listener.Name) +} + +func irTCPRouteName(listener *ListenerContext, tcpRoute *TCPRouteContext) string { return fmt.Sprintf("%s-%s-%s-%s", listener.gateway.Namespace, listener.gateway.Name, listener.Name, tcpRoute.Name) } diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index 600df376f25..5fb022bd250 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -621,9 +621,10 @@ func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resour // Create the TCP Listener while parsing the TLSRoute since // the listener directly links to a routeDestination. irListener := &ir.TCPListener{ - Name: irTLSListenerName(listener, tlsRoute), - Address: "0.0.0.0", - Port: uint32(containerPort), + ListenerName: irTCPListenerName(listener), + RouteName: irTLSRouteName(listener, tlsRoute), + Address: "0.0.0.0", + Port: uint32(containerPort), TLS: &ir.TLSInspectorConfig{ SNIs: hosts, }, @@ -903,7 +904,8 @@ func (t *Translator) processTCPRouteParentRefs(tcpRoute *TCPRouteContext, resour // Create the TCP Listener while parsing the TCPRoute since // the listener directly links to a routeDestination. irListener := &ir.TCPListener{ - Name: irTCPListenerName(listener, tcpRoute), + ListenerName: irTCPListenerName(listener), + RouteName: irTCPRouteName(listener, tcpRoute), Address: "0.0.0.0", Port: uint32(containerPort), Destinations: routeDestinations, diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml index 2bc8a5e24e6..a7bdba29eb9 100644 --- a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-terminate-and-passthrough.out.yaml @@ -141,7 +141,8 @@ xdsIR: port: 8080 weight: 1 tcp: - - name: envoy-gateway-gateway-1-tls-passthrough-tlsroute-1 + - listenerName: envoy-gateway-gateway-1-tls-passthrough + routeName: envoy-gateway-gateway-1-tls-passthrough-tlsroute-1 address: 0.0.0.0 port: 10090 tls: diff --git a/internal/gatewayapi/testdata/gateway-with-single-listener-with-multiple-tcproutes.out.yaml b/internal/gatewayapi/testdata/gateway-with-single-listener-with-multiple-tcproutes.out.yaml index 306fec79cae..8943991f607 100644 --- a/internal/gatewayapi/testdata/gateway-with-single-listener-with-multiple-tcproutes.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-single-listener-with-multiple-tcproutes.out.yaml @@ -89,7 +89,8 @@ tcpRoutes: xdsIR: envoy-gateway-gateway-1: tcp: - - name: "envoy-gateway-gateway-1-tcp-tcproute-1" + - listenerName: envoy-gateway-gateway-1-tcp + routeName: envoy-gateway-gateway-1-tcp-tcproute-1 address: "0.0.0.0" port: 10162 destinations: diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-on-same-tcp-port.out.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-on-same-tcp-port.out.yaml index 68f4f4441fd..2efa9be831b 100644 --- a/internal/gatewayapi/testdata/gateway-with-two-listeners-on-same-tcp-port.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-on-same-tcp-port.out.yaml @@ -81,7 +81,8 @@ tcpRoutes: xdsIR: envoy-gateway-gateway-1: tcp: - - name: "envoy-gateway-gateway-1-tcp1-tcproute-1" + - listenerName: envoy-gateway-gateway-1-tcp1 + routeName: envoy-gateway-gateway-1-tcp1-tcproute-1 address: "0.0.0.0" port: 10162 destinations: diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-http-tcp-protocol.out.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-http-tcp-protocol.out.yaml index 9376fe5fc0c..e83d902b7f5 100644 --- a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-http-tcp-protocol.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-same-port-http-tcp-protocol.out.yaml @@ -130,7 +130,8 @@ xdsIR: port: 8080 weight: 1 tcp: - - name: "envoy-gateway-gateway-1-tcp-tcproute-1" + - listenerName: envoy-gateway-gateway-1-tcp + routeName: envoy-gateway-gateway-1-tcp-tcproute-1 address: "0.0.0.0" port: 10080 destinations: diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-with-sectionname.out.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-with-sectionname.out.yaml index 7f27e8b0fc6..29b973d4cc8 100644 --- a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-with-sectionname.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-with-sectionname.out.yaml @@ -113,13 +113,15 @@ tcpRoutes: xdsIR: envoy-gateway-gateway-1: tcp: - - name: "envoy-gateway-gateway-1-tcp1-tcproute-1" + - listenerName: envoy-gateway-gateway-1-tcp1 + routeName: envoy-gateway-gateway-1-tcp1-tcproute-1 address: "0.0.0.0" port: 10162 destinations: - host: "7.7.7.7" port: 8163 - - name: "envoy-gateway-gateway-1-tcp2-tcproute-2" + - listenerName: envoy-gateway-gateway-1-tcp2 + routeName: envoy-gateway-gateway-1-tcp2-tcproute-2 address: "0.0.0.0" port: 10163 destinations: diff --git a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-without-sectionname.out.yaml b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-without-sectionname.out.yaml index 47bc975712f..8ad9d0009da 100644 --- a/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-without-sectionname.out.yaml +++ b/internal/gatewayapi/testdata/gateway-with-two-listeners-with-tcproutes-without-sectionname.out.yaml @@ -109,13 +109,15 @@ tcpRoutes: xdsIR: envoy-gateway-gateway-1: tcp: - - name: "envoy-gateway-gateway-1-tcp1-tcproute-1" + - listenerName: envoy-gateway-gateway-1-tcp1 + routeName: envoy-gateway-gateway-1-tcp1-tcproute-1 address: "0.0.0.0" port: 10161 destinations: - host: "7.7.7.7" port: 8163 - - name: "envoy-gateway-gateway-1-tcp2-tcproute-1" + - listenerName: envoy-gateway-gateway-1-tcp2 + routeName: envoy-gateway-gateway-1-tcp2-tcproute-1 address: "0.0.0.0" port: 10162 destinations: diff --git a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml index e5fb79bea1a..f4e92d691ac 100644 --- a/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-attaching-to-gateway.out.yaml @@ -64,7 +64,8 @@ tlsRoutes: xdsIR: envoy-gateway-gateway-1: tcp: - - name: envoy-gateway-gateway-1-tls-tlsroute-1 + - listenerName: envoy-gateway-gateway-1-tls + routeName: envoy-gateway-gateway-1-tls-tlsroute-1 address: 0.0.0.0 port: 10090 tls: diff --git a/internal/gatewayapi/testdata/tlsroute-multiple.out.yaml b/internal/gatewayapi/testdata/tlsroute-multiple.out.yaml index a0b74436144..ba67982e0ae 100644 --- a/internal/gatewayapi/testdata/tlsroute-multiple.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-multiple.out.yaml @@ -95,7 +95,8 @@ tlsRoutes: xdsIR: envoy-gateway-gateway-1: tcp: - - name: envoy-gateway-gateway-1-tls-tlsroute-1 + - listenerName: envoy-gateway-gateway-1-tls + routeName: envoy-gateway-gateway-1-tls-tlsroute-1 address: 0.0.0.0 port: 10091 tls: @@ -105,7 +106,8 @@ xdsIR: - host: 7.7.7.7 port: 8080 weight: 1 - - name: envoy-gateway-gateway-1-tls-tlsroute-2 + - listenerName: envoy-gateway-gateway-1-tls + routeName: envoy-gateway-gateway-1-tls-tlsroute-2 address: 0.0.0.0 port: 10091 tls: diff --git a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml index 7b8992d0d0f..a17be7e9c0c 100644 --- a/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-with-backendref-in-other-namespace-allowed-by-refgrant.out.yaml @@ -65,7 +65,8 @@ tlsRoutes: xdsIR: envoy-gateway-gateway-1: tcp: - - name: envoy-gateway-gateway-1-tls-tlsroute-1 + - listenerName: envoy-gateway-gateway-1-tls + routeName: envoy-gateway-gateway-1-tls-tlsroute-1 address: 0.0.0.0 port: 10090 tls: diff --git a/internal/gatewayapi/testdata/tlsroute-with-empty-hostname.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-empty-hostname.out.yaml index 19c25f7a5c5..c8886799847 100644 --- a/internal/gatewayapi/testdata/tlsroute-with-empty-hostname.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-with-empty-hostname.out.yaml @@ -63,7 +63,8 @@ tlsRoutes: xdsIR: envoy-gateway-gateway-1: tcp: - - name: envoy-gateway-gateway-1-tls-tlsroute-1 + - listenerName: envoy-gateway-gateway-1-tls + routeName: envoy-gateway-gateway-1-tls-tlsroute-1 address: 0.0.0.0 port: 10091 tls: diff --git a/internal/gatewayapi/testdata/tlsroute-with-empty-listener-hostname.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-empty-listener-hostname.out.yaml index b7a1954dd90..a77480151db 100644 --- a/internal/gatewayapi/testdata/tlsroute-with-empty-listener-hostname.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-with-empty-listener-hostname.out.yaml @@ -65,7 +65,8 @@ tlsRoutes: xdsIR: envoy-gateway-gateway-1: tcp: - - name: envoy-gateway-gateway-1-tls-tlsroute-1 + - listenerName: envoy-gateway-gateway-1-tls + routeName: envoy-gateway-gateway-1-tls-tlsroute-1 address: 0.0.0.0 port: 10091 tls: diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 648f5b6765d..b418680dbcf 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -23,6 +23,7 @@ var ( ErrListenerPortInvalid = errors.New("field Port specified is invalid") ErrHTTPListenerHostnamesEmpty = errors.New("field Hostnames must be specified with at least a single hostname entry") ErrTCPListenerSNIsEmpty = errors.New("field SNIs must be specified with at least a single server name entry") + ErrTCPListenerRouteNameEmpty = errors.New("field RouteName must be specified") ErrTLSServerCertEmpty = errors.New("field ServerCertificate must be specified") ErrTLSPrivateKey = errors.New("field PrivateKey must be specified") ErrHTTPRouteNameEmpty = errors.New("field Name must be specified") @@ -86,7 +87,7 @@ func (x Xds) GetHTTPListener(name string) *HTTPListener { func (x Xds) GetTCPListener(name string) *TCPListener { for _, listener := range x.TCP { - if listener.Name == name { + if listener.ListenerName == name { return listener } } @@ -602,8 +603,10 @@ func (s StringMatch) Validate() error { // TCPListener holds the TCP listener configuration. // +k8s:deepcopy-gen=true type TCPListener struct { - // Name of the TCPListener - Name string + // ListenerName of the TCPListener + ListenerName string + // RouteName of the tcp or tls route. + RouteName string // Address that the listener should listen on. Address string // Port on which the service can be expected to be accessed by clients. @@ -618,9 +621,12 @@ type TCPListener struct { // Validate the fields within the TCPListener structure func (h TCPListener) Validate() error { var errs error - if h.Name == "" { + if h.ListenerName == "" { errs = multierror.Append(errs, ErrListenerNameEmpty) } + if h.RouteName == "" { + errs = multierror.Append(errs, ErrTCPListenerRouteNameEmpty) + } if ip := net.ParseIP(h.Address); ip == nil { errs = multierror.Append(errs, ErrListenerAddressInvalid) } diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index dca8dffeb5b..e8d59b61245 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -66,32 +66,38 @@ var ( // TCPListener happyTCPListenerTLSPassthrough = TCPListener{ - Name: "happy", + ListenerName: "happy-listener", + RouteName: "happy-route", Address: "0.0.0.0", Port: 80, TLS: &TLSInspectorConfig{SNIs: []string{"example.com"}}, Destinations: []*RouteDestination{&happyRouteDestination}, } emptySNITCPListenerTLSPassthrough = TCPListener{ - Name: "empty-sni", + ListenerName: "empty-sni-listener", + RouteName: "empty-sni-route", Address: "0.0.0.0", Port: 80, Destinations: []*RouteDestination{&happyRouteDestination}, } invalidNameTCPListenerTLSPassthrough = TCPListener{ + RouteName: "invalid-listener-name-route", Address: "0.0.0.0", Port: 80, TLS: &TLSInspectorConfig{SNIs: []string{"example.com"}}, Destinations: []*RouteDestination{&happyRouteDestination}, } invalidAddrTCPListenerTLSPassthrough = TCPListener{ - Name: "invalid-addr", + ListenerName: "invalid-addr-listener", + RouteName: "invalid-addr-route", Address: "1.0.0", Port: 80, TLS: &TLSInspectorConfig{SNIs: []string{"example.com"}}, Destinations: []*RouteDestination{&happyRouteDestination}, } invalidSNITCPListenerTLSPassthrough = TCPListener{ + ListenerName: "invalid-sni-listener", + RouteName: "invalid-sni-route", Address: "0.0.0.0", Port: 80, TLS: &TLSInspectorConfig{SNIs: []string{}}, diff --git a/internal/xds/translator/testdata/in/xds-ir/multiple-listeners-same-port.yaml b/internal/xds/translator/testdata/in/xds-ir/multiple-listeners-same-port.yaml index 396cf06ec83..ecdbbf9bc68 100644 --- a/internal/xds/translator/testdata/in/xds-ir/multiple-listeners-same-port.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/multiple-listeners-same-port.yaml @@ -52,7 +52,8 @@ http: - host: "1.2.3.4" port: 50000 tcp: -- name: "fifth-listener" +- listenerName: "fifth-listener" + routeName: "fifth-listener" address: "0.0.0.0" port: 10080 tls: @@ -61,7 +62,8 @@ tcp: destinations: - host: "1.2.3.4" port: 50000 -- name: "sixth-listener" +- listenerName: "fifth-listener" + routeName: "sixth-listener" address: "0.0.0.0" port: 10080 tls: diff --git a/internal/xds/translator/testdata/in/xds-ir/multiple-simple-tcp-route-same-port.yaml b/internal/xds/translator/testdata/in/xds-ir/multiple-simple-tcp-route-same-port.yaml index 324de212838..7c945bbe111 100644 --- a/internal/xds/translator/testdata/in/xds-ir/multiple-simple-tcp-route-same-port.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/multiple-simple-tcp-route-same-port.yaml @@ -1,5 +1,6 @@ tcp: -- name: "tcp-route-simple" +- listenerName: "tcp" + routeName: "tcp-route-simple" address: "0.0.0.0" port: 10080 destinations: @@ -7,7 +8,8 @@ tcp: port: 50000 - host: "5.6.7.8" port: 50001 -- name: "tcp-route-simple-1" +- listenerName: "tcp" + routeName: "tcp-route-simple-1" address: "0.0.0.0" port: 10080 destinations: @@ -15,7 +17,8 @@ tcp: port: 50000 - host: "5.6.7.8" port: 50001 -- name: "tcp-route-simple-2" +- listenerName: "tcp" + routeName: "tcp-route-simple-2" address: "0.0.0.0" port: 10080 destinations: @@ -23,7 +26,8 @@ tcp: port: 50000 - host: "5.6.7.8" port: 50001 -- name: "tcp-route-simple-3" +- listenerName: "tcp" + routeName: "tcp-route-simple-3" address: "0.0.0.0" port: 10080 destinations: @@ -31,7 +35,8 @@ tcp: port: 50000 - host: "5.6.7.8" port: 50001 -- name: "tcp-route-simple-4" +- listenerName: "tcp" + routeName: "tcp-route-simple-4" address: "0.0.0.0" port: 10080 destinations: diff --git a/internal/xds/translator/testdata/in/xds-ir/tcp-route-complex.yaml b/internal/xds/translator/testdata/in/xds-ir/tcp-route-complex.yaml index 1de28530c7d..d2a61ebcc1c 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tcp-route-complex.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tcp-route-complex.yaml @@ -1,5 +1,6 @@ tcp: -- name: "tcp-route-complex" +- listenerName: "tcp" + routeName: "tcp-route-complex" address: "0.0.0.0" port: 10080 tls: diff --git a/internal/xds/translator/testdata/in/xds-ir/tcp-route-simple.yaml b/internal/xds/translator/testdata/in/xds-ir/tcp-route-simple.yaml index b88d57dd399..a1588d4c4a0 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tcp-route-simple.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tcp-route-simple.yaml @@ -1,5 +1,6 @@ tcp: -- name: "tcp-route-simple" +- listenerName: "tcp" + routeName: "tcp-route-simple" address: "0.0.0.0" port: 10080 destinations: diff --git a/internal/xds/translator/testdata/in/xds-ir/tcp-route-weighted-backend.yaml b/internal/xds/translator/testdata/in/xds-ir/tcp-route-weighted-backend.yaml index fe02dec82c7..858ed5b407b 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tcp-route-weighted-backend.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tcp-route-weighted-backend.yaml @@ -1,5 +1,6 @@ tcp: -- name: "tcp-route-weighted-backend" +- listenerName: "tcp" + routeName: "tcp-route-weighted-backend" address: "0.0.0.0" port: 10080 tls: diff --git a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml index c7f59633067..6cf82dc4eea 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml @@ -1,5 +1,6 @@ tcp: -- name: "tls-passthrough" +- listenerName: "tls" + routeName: "tls-passthrough" address: "0.0.0.0" port: 10080 tls: diff --git a/internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.listeners.yaml index 128fd616fca..c181a7e9eb2 100644 --- a/internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/multiple-simple-tcp-route-same-port.listeners.yaml @@ -67,4 +67,4 @@ path: /dev/stdout cluster: tcp-route-simple-4 statPrefix: tcp - name: tcp-route-simple + name: tcp diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.listeners.yaml index dfb2a30acf0..b43d0e2298f 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-complex.listeners.yaml @@ -32,4 +32,4 @@ - name: envoy.filters.listener.tls_inspector typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector - name: tcp-route-complex + name: tcp diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.listeners.yaml index 05e159719fb..61691ea9827 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-simple.listeners.yaml @@ -23,4 +23,4 @@ path: /dev/stdout cluster: tcp-route-simple statPrefix: tcp - name: tcp-route-simple + name: tcp diff --git a/internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.listeners.yaml index e16c3a23849..1447aeeb95c 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tcp-route-weighted-backend.listeners.yaml @@ -32,4 +32,4 @@ - name: envoy.filters.listener.tls_inspector typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector - name: tcp-route-weighted-backend + name: tcp diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml index 2186e91d327..03075b05d35 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml @@ -30,4 +30,4 @@ - name: envoy.filters.listener.tls_inspector typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector - name: tls-passthrough + name: tls diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index 5f792e4d9e8..bd37002f956 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -200,7 +200,7 @@ func processTCPListenerXdsTranslation(tCtx *types.ResourceVersionTable, tcpListe for _, tcpListener := range tcpListeners { // 1:1 between IR TCPListener and xDS Cluster addXdsCluster(tCtx, addXdsClusterArgs{ - name: tcpListener.Name, + name: tcpListener.RouteName, destinations: tcpListener.Destinations, tSocket: nil, protocol: DefaultProtocol, @@ -210,11 +210,11 @@ func processTCPListenerXdsTranslation(tCtx *types.ResourceVersionTable, tcpListe // Search for an existing listener, if it does not exist, create one. xdsListener := findXdsListener(tCtx, tcpListener.Address, tcpListener.Port, corev3.SocketAddress_TCP) if xdsListener == nil { - xdsListener = buildXdsTCPListener(tcpListener.Name, tcpListener.Address, tcpListener.Port) + xdsListener = buildXdsTCPListener(tcpListener.ListenerName, tcpListener.Address, tcpListener.Port) tCtx.AddXdsResource(resourcev3.ListenerType, xdsListener) } - if err := addXdsTCPFilterChain(xdsListener, tcpListener, tcpListener.Name); err != nil { + if err := addXdsTCPFilterChain(xdsListener, tcpListener, tcpListener.RouteName); err != nil { return err } } From d8e77b459dd378f06988362a277c6e78f801b6a5 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Thu, 6 Jul 2023 16:42:23 -0400 Subject: [PATCH 08/47] Add support ALPN routing via TLSRoute annotation --- internal/gatewayapi/route.go | 13 ++++++++- .../testdata/tlsroute-multiple.in.yaml | 2 ++ .../testdata/tlsroute-multiple.out.yaml | 8 ++++-- internal/ir/xds.go | 6 ++++ internal/xds/translator/listener.go | 28 ++++++++++++++++++- .../in/xds-ir/tls-route-passthrough.yaml | 2 ++ .../tls-route-passthrough.listeners.yaml | 2 ++ 7 files changed, 57 insertions(+), 4 deletions(-) diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index 5fb022bd250..764224a9acc 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -16,6 +16,11 @@ import ( "github.com/envoyproxy/gateway/internal/ir" ) +const ( + // AnnotationTLSRouteProtos specifies the ALPN protos matched by a TLSRoute. + AnnotationTLSRouteProtos = "cloud.teleport.dev/protos" +) + var _ RoutesTranslator = (*Translator)(nil) type RoutesTranslator interface { @@ -607,6 +612,11 @@ func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resour continue } + var protos []string + if v := tlsRoute.Annotations[AnnotationTLSRouteProtos]; v != "" { + protos = strings.Split(tlsRoute.Annotations[AnnotationTLSRouteProtos], ",") + } + var hasHostnameIntersection bool for _, listener := range parentRef.listeners { hosts := computeHosts(tlsRoute.GetHostnames(), listener.Hostname) @@ -626,7 +636,8 @@ func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resour Address: "0.0.0.0", Port: uint32(containerPort), TLS: &ir.TLSInspectorConfig{ - SNIs: hosts, + SNIs: hosts, + Protos: protos, }, Destinations: routeDestinations, } diff --git a/internal/gatewayapi/testdata/tlsroute-multiple.in.yaml b/internal/gatewayapi/testdata/tlsroute-multiple.in.yaml index f7c756f0cca..9ceecf239af 100644 --- a/internal/gatewayapi/testdata/tlsroute-multiple.in.yaml +++ b/internal/gatewayapi/testdata/tlsroute-multiple.in.yaml @@ -21,6 +21,8 @@ tlsRoutes: metadata: namespace: default name: tlsroute-1 + annotations: + "cloud.teleport.dev/protos": "customproto" spec: parentRefs: - namespace: envoy-gateway diff --git a/internal/gatewayapi/testdata/tlsroute-multiple.out.yaml b/internal/gatewayapi/testdata/tlsroute-multiple.out.yaml index ba67982e0ae..0185935c383 100644 --- a/internal/gatewayapi/testdata/tlsroute-multiple.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-multiple.out.yaml @@ -21,7 +21,7 @@ gateways: supportedKinds: - group: gateway.networking.k8s.io kind: TLSRoute - attachedRoutes: 2 + attachedRoutes: 2 conditions: - type: Programmed status: "True" @@ -37,6 +37,8 @@ tlsRoutes: metadata: namespace: default name: tlsroute-1 + annotations: + "cloud.teleport.dev/protos": "customproto" spec: parentRefs: - namespace: envoy-gateway @@ -102,6 +104,8 @@ xdsIR: tls: snis: - foo.com + protos: + - "customproto" destinations: - host: 7.7.7.7 port: 8080 @@ -116,7 +120,7 @@ xdsIR: destinations: - host: 7.7.7.7 port: 8080 - weight: 1 + weight: 1 infraIR: envoy-gateway-gateway-1: proxy: diff --git a/internal/ir/xds.go b/internal/ir/xds.go index b418680dbcf..b514fcb12fe 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -655,6 +655,12 @@ type TLSInspectorConfig struct { // supported, and values like *w.example.com are invalid. // SNIs are used only in case of TLS Passthrough. SNIs []string + // Protos specifies application protocols that are compared against the + // ALPN protocols of a new connection. + // Wildcard protocols are supported in the prefix form. + // Partial wildcards are not supported. + // Protos are used only in case of TLS Passthrough. + Protos []string } func (t TLSInspectorConfig) Validate() error { diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go index 17a2ae0704b..a5085e271a6 100644 --- a/internal/xds/translator/listener.go +++ b/internal/xds/translator/listener.go @@ -183,6 +183,32 @@ func addServerNamesMatch(xdsListener *listenerv3.Listener, filterChain *listener return nil } +func addSNIMatch(xdsListener *listenerv3.Listener, filterChain *listenerv3.FilterChain, snis, protos []string) error { + var serverNames, applicationProtocols []string + + // Don't add a filter chain match if the hostname is a wildcard character. + if len(snis) > 0 && snis[0] != "*" { + serverNames = snis + } + // Don't add a filter chain match if the proto is a wildcard character. + if len(protos) > 0 && protos[0] != "*" { + applicationProtocols = protos + } + + if serverNames != nil || applicationProtocols != nil { + filterChain.FilterChainMatch = &listenerv3.FilterChainMatch{ + ServerNames: serverNames, + ApplicationProtocols: applicationProtocols, + } + + if err := addXdsTLSInspectorFilter(xdsListener); err != nil { + return err + } + } + + return nil +} + // findXdsHTTPRouteConfigName finds the name of the route config associated with the // http connection manager within the default filter chain and returns an empty string if // not found. @@ -249,7 +275,7 @@ func addXdsTCPFilterChain(xdsListener *listenerv3.Listener, irListener *ir.TCPLi } if irListener.TLS != nil { - if err := addServerNamesMatch(xdsListener, filterChain, irListener.TLS.SNIs); err != nil { + if err := addSNIMatch(xdsListener, filterChain, irListener.TLS.SNIs, irListener.TLS.Protos); err != nil { return err } } diff --git a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml index 6cf82dc4eea..9cba8ca0956 100644 --- a/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/tls-route-passthrough.yaml @@ -6,6 +6,8 @@ tcp: tls: snis: - foo.com + protos: + - customproto destinations: - host: "1.2.3.4" port: 50000 diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml index 03075b05d35..233ec1d7cdd 100644 --- a/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-passthrough.listeners.yaml @@ -13,6 +13,8 @@ portValue: 10080 filterChains: - filterChainMatch: + applicationProtocols: + - customproto serverNames: - foo.com filters: From 9e25185d201fdecd44978641b518ca29bc24d577 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Thu, 6 Jul 2023 16:51:06 -0400 Subject: [PATCH 09/47] Improve tests --- internal/gatewayapi/testdata/tlsroute-multiple.in.yaml | 2 +- internal/gatewayapi/testdata/tlsroute-multiple.out.yaml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/gatewayapi/testdata/tlsroute-multiple.in.yaml b/internal/gatewayapi/testdata/tlsroute-multiple.in.yaml index 9ceecf239af..2d94ff839eb 100644 --- a/internal/gatewayapi/testdata/tlsroute-multiple.in.yaml +++ b/internal/gatewayapi/testdata/tlsroute-multiple.in.yaml @@ -22,7 +22,7 @@ tlsRoutes: namespace: default name: tlsroute-1 annotations: - "cloud.teleport.dev/protos": "customproto" + "cloud.teleport.dev/protos": "proto1,proto2" spec: parentRefs: - namespace: envoy-gateway diff --git a/internal/gatewayapi/testdata/tlsroute-multiple.out.yaml b/internal/gatewayapi/testdata/tlsroute-multiple.out.yaml index 0185935c383..ffe07954979 100644 --- a/internal/gatewayapi/testdata/tlsroute-multiple.out.yaml +++ b/internal/gatewayapi/testdata/tlsroute-multiple.out.yaml @@ -38,7 +38,7 @@ tlsRoutes: namespace: default name: tlsroute-1 annotations: - "cloud.teleport.dev/protos": "customproto" + "cloud.teleport.dev/protos": "proto1,proto2" spec: parentRefs: - namespace: envoy-gateway @@ -105,7 +105,8 @@ xdsIR: snis: - foo.com protos: - - "customproto" + - "proto1" + - "proto2" destinations: - host: 7.7.7.7 port: 8080 From 6ac1a3d9c8d2b6a0e57e546695d0ef744ea37f79 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Thu, 6 Jul 2023 16:56:13 -0400 Subject: [PATCH 10/47] Fix variable --- internal/gatewayapi/route.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index 764224a9acc..70869b91f3a 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -614,7 +614,7 @@ func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resour var protos []string if v := tlsRoute.Annotations[AnnotationTLSRouteProtos]; v != "" { - protos = strings.Split(tlsRoute.Annotations[AnnotationTLSRouteProtos], ",") + protos = strings.Split(v, ",") } var hasHostnameIntersection bool From ed681cfe97fd25caaf9ca2aa08829b85636f6f69 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Thu, 6 Jul 2023 17:11:53 -0400 Subject: [PATCH 11/47] Add missing deepcopy regen --- internal/ir/zz_generated.deepcopy.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 3fd25eac2a1..1cd9b30e935 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -661,6 +661,11 @@ func (in *TLSInspectorConfig) DeepCopyInto(out *TLSInspectorConfig) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Protos != nil { + in, out := &in.Protos, &out.Protos + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSInspectorConfig. From fc498d88ece4593950edec99f36c90f5c2019349 Mon Sep 17 00:00:00 2001 From: David Boslee Date: Mon, 10 Jul 2023 10:19:34 -0600 Subject: [PATCH 12/47] Add TLSRoute annotations to handle proxy protocol and max connections (#4) --- internal/gatewayapi/route.go | 37 ++++++- .../tlsroute-with-upstream-config.in.yaml | 46 ++++++++ .../tlsroute-with-upstream-config.out.yaml | 100 ++++++++++++++++++ internal/ir/xds.go | 10 ++ internal/ir/zz_generated.deepcopy.go | 1 + internal/xds/translator/cluster.go | 20 +++- internal/xds/translator/cluster_test.go | 2 +- .../tls-route-with-upstream-config.yaml | 14 +++ ...s-route-with-upstream-config.clusters.yaml | 25 +++++ ...-route-with-upstream-config.endpoints.yaml | 10 ++ ...-route-with-upstream-config.listeners.yaml | 33 ++++++ ...tls-route-with-upstream-config.routes.yaml | 1 + internal/xds/translator/translator.go | 82 ++++++++++++-- internal/xds/translator/translator_test.go | 3 + 14 files changed, 370 insertions(+), 14 deletions(-) create mode 100644 internal/gatewayapi/testdata/tlsroute-with-upstream-config.in.yaml create mode 100644 internal/gatewayapi/testdata/tlsroute-with-upstream-config.out.yaml create mode 100644 internal/xds/translator/testdata/in/xds-ir/tls-route-with-upstream-config.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.endpoints.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.routes.yaml diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index 70869b91f3a..d285fadfc21 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -7,9 +7,11 @@ package gatewayapi import ( "fmt" + "strconv" "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/gateway-api/apis/v1alpha2" "sigs.k8s.io/gateway-api/apis/v1beta1" @@ -17,6 +19,13 @@ import ( ) const ( + // AnnotationRouteUpstreamProxyProtocol enables proxy protocol for a given route. Currently + // only TLSRoutes support this annotation. The value is expected to be set to "true", + // case-insensitive, to enable proxy protocol. All other values will be ignored. + AnnotationRouteUpstreamProxyProtocol = "cloud.teleport.dev/upstream-proxy-protocol" + // AnnotationRouteUpstreamMaxConnections specifies the max upstream connections for a given + // route. Currently only TLSRoutes support this annotation. The value must be a valid uint32. + AnnotationRouteUpstreamMaxConnections = "cloud.teleport.dev/upstream-max-connections" // AnnotationTLSRouteProtos specifies the ALPN protos matched by a TLSRoute. AnnotationTLSRouteProtos = "cloud.teleport.dev/protos" ) @@ -558,6 +567,30 @@ func (t *Translator) ProcessTLSRoutes(tlsRoutes []*v1alpha2.TLSRoute, gateways [ return relevantTLSRoutes } +func getUpstreamConfig(route client.Object) ir.UpstreamConfig { + var ( + enableProxyProtocol bool + maxConns uint32 + ) + + annotations := route.GetAnnotations() + if v := annotations[AnnotationRouteUpstreamProxyProtocol]; strings.ToLower(v) == "true" { + enableProxyProtocol = true + } + + if v := annotations[AnnotationRouteUpstreamMaxConnections]; v != "" { + uInt, err := strconv.ParseUint(v, 10, 32) + if err == nil { + maxConns = uint32(uInt) + } + } + + return ir.UpstreamConfig{ + EnableProxyProtocol: enableProxyProtocol, + MaxConnections: maxConns, + } +} + func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resources *Resources, xdsIR XdsIRMap) { for _, parentRef := range tlsRoute.parentRefs { @@ -639,8 +672,10 @@ func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resour SNIs: hosts, Protos: protos, }, - Destinations: routeDestinations, + Destinations: routeDestinations, + UpstreamConfig: getUpstreamConfig(tlsRoute), } + gwXdsIR := xdsIR[irKey] gwXdsIR.TCP = append(gwXdsIR.TCP, irListener) diff --git a/internal/gatewayapi/testdata/tlsroute-with-upstream-config.in.yaml b/internal/gatewayapi/testdata/tlsroute-with-upstream-config.in.yaml new file mode 100644 index 00000000000..b0267fc6b70 --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-with-upstream-config.in.yaml @@ -0,0 +1,46 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + port: 91 + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All +tlsRoutes: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + annotations: + cloud.teleport.dev/upstream-proxy-protocol: "true" + cloud.teleport.dev/upstream-max-connections: "10000" + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + hostnames: + - "foo.com" + rules: + - backendRefs: + - name: service-1 + port: 8080 +services: +- apiVersion: v1 + kind: Service + metadata: + namespace: default + name: service-1 + spec: + clusterIP: 7.7.7.7 + ports: + - port: 8080 diff --git a/internal/gatewayapi/testdata/tlsroute-with-upstream-config.out.yaml b/internal/gatewayapi/testdata/tlsroute-with-upstream-config.out.yaml new file mode 100644 index 00000000000..96059870adb --- /dev/null +++ b/internal/gatewayapi/testdata/tlsroute-with-upstream-config.out.yaml @@ -0,0 +1,100 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls + protocol: TLS + port: 91 + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tls + supportedKinds: + - group: gateway.networking.k8s.io + kind: TLSRoute + attachedRoutes: 1 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Sending translated listener configuration to the data plane + - type: Accepted + status: "True" + reason: Accepted + message: Listener has been successfully translated +tlsRoutes: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + annotations: + cloud.teleport.dev/upstream-proxy-protocol: "true" + cloud.teleport.dev/upstream-max-connections: "10000" + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + hostnames: + - foo.com + rules: + - backendRefs: + - name: service-1 + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted + - type: ResolvedRefs + status: "True" + reason: ResolvedRefs + message: Resolved all the Object references for the Route +xdsIR: + envoy-gateway-gateway-1: + tcp: + - listenerName: envoy-gateway-gateway-1-tls + routeName: envoy-gateway-gateway-1-tls-tlsroute-1 + address: 0.0.0.0 + port: 10091 + tls: + snis: + - foo.com + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 + upstreamConfig: + enableProxyProtocol: true + maxConnections: 10000 +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway-gateway-1 + + listeners: + - address: "" + ports: + - name: tls + protocol: "TLS" + servicePort: 91 + containerPort: 10091 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index b514fcb12fe..c9e92e1ba74 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -616,6 +616,16 @@ type TCPListener struct { TLS *TLSInspectorConfig // Destinations associated with TCP traffic to the service. Destinations []*RouteDestination + // UpstreamConfig provides configuration for upstream connections. + UpstreamConfig UpstreamConfig +} + +// UpstreamConfig holds the upstream cluster configuration. +type UpstreamConfig struct { + // EnableProxyProtocol enables proxy protocol v2 for connections to upstream endpoints. + EnableProxyProtocol bool + // MaxConnections sets the maximum open and pending connections to an upstream endpoint. + MaxConnections uint32 } // Validate the fields within the TCPListener structure diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 1cd9b30e935..3e5a8562036 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -641,6 +641,7 @@ func (in *TCPListener) DeepCopyInto(out *TCPListener) { } } } + out.UpstreamConfig = in.UpstreamConfig } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPListener. diff --git a/internal/xds/translator/cluster.go b/internal/xds/translator/cluster.go index a387c5c30b3..f3b20b1b12e 100644 --- a/internal/xds/translator/cluster.go +++ b/internal/xds/translator/cluster.go @@ -13,6 +13,7 @@ import ( endpointv3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" httpv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3" "github.com/envoyproxy/go-control-plane/pkg/resource/v3" + "github.com/golang/protobuf/ptypes/wrappers" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/wrapperspb" @@ -24,7 +25,20 @@ const ( extensionOptionsKey = "envoy.extensions.upstreams.http.v3.HttpProtocolOptions" ) -func buildXdsCluster(routeName string, tSocket *corev3.TransportSocket, protocol ProtocolType, endpointType EndpointType) *clusterv3.Cluster { +func buildXdsClusterCircuitBreaker(maxConns uint32) *clusterv3.CircuitBreakers { + return &clusterv3.CircuitBreakers{ + // Increase default connection limits per upstream cluster from 1024. + Thresholds: []*clusterv3.CircuitBreakers_Thresholds{ + { + Priority: corev3.RoutingPriority_DEFAULT, + MaxConnections: &wrappers.UInt32Value{Value: maxConns}, + MaxPendingRequests: wrapperspb.UInt32(maxConns), + }, + }, + } +} + +func buildXdsCluster(routeName string, tSocket *corev3.TransportSocket, protocol ProtocolType, endpointType EndpointType, cbs *clusterv3.CircuitBreakers) *clusterv3.Cluster { clusterName := routeName cluster := &clusterv3.Cluster{ Name: clusterName, @@ -41,6 +55,10 @@ func buildXdsCluster(routeName string, tSocket *corev3.TransportSocket, protocol cluster.TransportSocket = tSocket } + if cbs != nil { + cluster.CircuitBreakers = cbs + } + if endpointType == Static { cluster.ClusterDiscoveryType = &clusterv3.Cluster_Type{Type: clusterv3.Cluster_EDS} cluster.EdsClusterConfig = &clusterv3.Cluster_EdsClusterConfig{ diff --git a/internal/xds/translator/cluster_test.go b/internal/xds/translator/cluster_test.go index 2f709cc0f95..e8f4641391a 100644 --- a/internal/xds/translator/cluster_test.go +++ b/internal/xds/translator/cluster_test.go @@ -28,7 +28,7 @@ const ( func TestBuildXdsCluster(t *testing.T) { bootstrapXdsCluster := getXdsClusterObjFromBootstrap(t) - dynamicXdsCluster := buildXdsCluster(bootstrapXdsCluster.Name, bootstrapXdsCluster.TransportSocket, HTTP2, DefaultEndpointType) + dynamicXdsCluster := buildXdsCluster(bootstrapXdsCluster.Name, bootstrapXdsCluster.TransportSocket, HTTP2, DefaultEndpointType, nil) require.Equal(t, bootstrapXdsCluster.Name, dynamicXdsCluster.Name) require.Equal(t, bootstrapXdsCluster.ClusterDiscoveryType, dynamicXdsCluster.ClusterDiscoveryType) diff --git a/internal/xds/translator/testdata/in/xds-ir/tls-route-with-upstream-config.yaml b/internal/xds/translator/testdata/in/xds-ir/tls-route-with-upstream-config.yaml new file mode 100644 index 00000000000..1436fa445e0 --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/tls-route-with-upstream-config.yaml @@ -0,0 +1,14 @@ +tcp: +- listenerName: "tls-upstream-config-listener" + routeName: "tls-upstream-config-route" + address: "0.0.0.0" + port: 10080 + tls: + snis: + - foo.com + destinations: + - host: "1.2.3.4" + port: 50000 + upstreamConfig: + enableProxyProtocol: true + maxConnections: 10000 diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.clusters.yaml new file mode 100644 index 00000000000..c987b2ebd9a --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.clusters.yaml @@ -0,0 +1,25 @@ +- circuitBreakers: + thresholds: + - maxConnections: 10000 + maxPendingRequests: 10000 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + name: tls-upstream-config-route + outlierDetection: {} + transportSocket: + name: envoy.transport_sockets.upstream_proxy_protocol + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.proxy_protocol.v3.ProxyProtocolUpstreamTransport + config: + version: V2 + transportSocket: + name: envoy.transport_sockets.raw_buffer + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.raw_buffer.v3.RawBuffer + type: EDS diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.endpoints.yaml new file mode 100644 index 00000000000..903438d4f08 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.endpoints.yaml @@ -0,0 +1,10 @@ +- clusterName: tls-upstream-config-route + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + locality: {} diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.listeners.yaml new file mode 100644 index 00000000000..721dfe5781a --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.listeners.yaml @@ -0,0 +1,33 @@ +- accessLog: + - filter: + responseFlagFilter: + flags: + - NR + name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + filterChains: + - filterChainMatch: + serverNames: + - foo.com + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + accessLog: + - name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + cluster: tls-upstream-config-route + statPrefix: passthrough + listenerFilters: + - name: envoy.filters.listener.tls_inspector + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector + name: tls-upstream-config-listener diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.routes.yaml new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-upstream-config.routes.yaml @@ -0,0 +1 @@ +[] diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index bd37002f956..e5dd76e2ebd 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -13,8 +13,11 @@ import ( corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + proxyprotocolv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/proxy_protocol/v3" + rawbufferv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/raw_buffer/v3" resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" "github.com/tetratelabs/multierror" + "google.golang.org/protobuf/types/known/anypb" extensionTypes "github.com/envoyproxy/gateway/internal/extension/types" "github.com/envoyproxy/gateway/internal/ir" @@ -198,13 +201,36 @@ func (t *Translator) processHTTPListenerXdsTranslation(tCtx *types.ResourceVersi func processTCPListenerXdsTranslation(tCtx *types.ResourceVersionTable, tcpListeners []*ir.TCPListener) error { for _, tcpListener := range tcpListeners { + + // TODO(dboslee):Keep an eye on + // https://github.com/envoyproxy/gateway/issues/1328 and + // https://github.com/kubernetes-sigs/gateway-api/issues/1955 + // to see if proxy protocol support lands in gateway-api or envoyproxy/gateway. + + var ( + tSocket *corev3.TransportSocket + cbs *clusterv3.CircuitBreakers + err error + ) + if tcpListener.UpstreamConfig.EnableProxyProtocol { + tSocket, err = buildXdsProxyProtocolTCPSocket() + if err != nil { + return err + } + } + + if maxConns := tcpListener.UpstreamConfig.MaxConnections; maxConns > 0 { + cbs = buildXdsClusterCircuitBreaker(maxConns) + } + // 1:1 between IR TCPListener and xDS Cluster addXdsCluster(tCtx, addXdsClusterArgs{ - name: tcpListener.RouteName, - destinations: tcpListener.Destinations, - tSocket: nil, - protocol: DefaultProtocol, - endpoint: Static, + name: tcpListener.RouteName, + destinations: tcpListener.Destinations, + tSocket: tSocket, + protocol: DefaultProtocol, + endpoint: Static, + circuitBreakers: cbs, }) // Search for an existing listener, if it does not exist, create one. @@ -221,6 +247,39 @@ func processTCPListenerXdsTranslation(tCtx *types.ResourceVersionTable, tcpListe return nil } +func buildXdsProxyProtocolTCPSocket() (*corev3.TransportSocket, error) { + rawBuffer := &rawbufferv3.RawBuffer{} + rawBufferAny, err := anypb.New(rawBuffer) + if err != nil { + return nil, err + } + + proxyProtocol := &proxyprotocolv3.ProxyProtocolUpstreamTransport{ + Config: &corev3.ProxyProtocolConfig{ + Version: corev3.ProxyProtocolConfig_V2, + }, + TransportSocket: &corev3.TransportSocket{ + Name: "envoy.transport_sockets.raw_buffer", + ConfigType: &corev3.TransportSocket_TypedConfig{ + TypedConfig: rawBufferAny, + }, + }, + } + + proxyProtocolAny, err := anypb.New(proxyProtocol) + if err != nil { + return nil, err + } + + tSocket := &corev3.TransportSocket{ + Name: "envoy.transport_sockets.upstream_proxy_protocol", + ConfigType: &corev3.TransportSocket_TypedConfig{ + TypedConfig: proxyProtocolAny, + }, + } + return tSocket, nil +} + func processUDPListenerXdsTranslation(tCtx *types.ResourceVersionTable, udpListeners []*ir.UDPListener) error { for _, udpListener := range udpListeners { // 1:1 between IR UDPListener and xDS Cluster @@ -280,7 +339,7 @@ func findXdsCluster(tCtx *types.ResourceVersionTable, name string) *clusterv3.Cl } func addXdsCluster(tCtx *types.ResourceVersionTable, args addXdsClusterArgs) { - xdsCluster := buildXdsCluster(args.name, args.tSocket, args.protocol, args.endpoint) + xdsCluster := buildXdsCluster(args.name, args.tSocket, args.protocol, args.endpoint, args.circuitBreakers) xdsEndpoints := buildXdsClusterLoadAssignment(args.name, args.destinations) // Use EDS for static endpoints if args.endpoint == Static { @@ -308,11 +367,12 @@ func findXdsRouteConfig(tCtx *types.ResourceVersionTable, name string) *routev3. } type addXdsClusterArgs struct { - name string - destinations []*ir.RouteDestination - tSocket *corev3.TransportSocket - protocol ProtocolType - endpoint EndpointType + name string + destinations []*ir.RouteDestination + tSocket *corev3.TransportSocket + protocol ProtocolType + endpoint EndpointType + circuitBreakers *clusterv3.CircuitBreakers } type ProtocolType int diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go index db98fc0311f..7757a4d2983 100644 --- a/internal/xds/translator/translator_test.go +++ b/internal/xds/translator/translator_test.go @@ -75,6 +75,9 @@ func TestTranslateXds(t *testing.T) { { name: "tls-route-passthrough", }, + { + name: "tls-route-with-upstream-config", + }, { name: "tcp-route-simple", }, From 19ab8260e5f070e9af781f955e31389ae24ee87c Mon Sep 17 00:00:00 2001 From: David Boslee Date: Mon, 10 Jul 2023 16:43:20 -0600 Subject: [PATCH 13/47] Cherry pick support for envoy pod affinity and tolerations (#7) --- api/config/v1alpha1/shared_types.go | 8 + api/config/v1alpha1/zz_generated.deepcopy.go | 12 + ...ig.gateway.envoyproxy.io_envoyproxies.yaml | 1082 +++++++++++++++++ docs/latest/api/config_types.md | 2 + .../testdata/envoyproxy-valid.in.yaml | 14 + .../testdata/envoyproxy-valid.out.yaml | 14 + .../kubernetes/proxy_deployment.go | 2 + .../kubernetes/ratelimit_deployment.go | 2 + tools/make/teleport.mk | 2 - 9 files changed, 1136 insertions(+), 2 deletions(-) diff --git a/api/config/v1alpha1/shared_types.go b/api/config/v1alpha1/shared_types.go index e270d2acf5d..71d27a7dfd5 100644 --- a/api/config/v1alpha1/shared_types.go +++ b/api/config/v1alpha1/shared_types.go @@ -75,6 +75,14 @@ type KubernetesPodSpec struct { // // +optional SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` + + // If specified, the pod's scheduling constraints. + // +optional + Affinity *corev1.Affinity `json:"affinity,omitempty"` + + // If specified, the pod's tolerations. + // +optional + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` } // KubernetesContainerSpec defines the desired state of the Kubernetes container resource. diff --git a/api/config/v1alpha1/zz_generated.deepcopy.go b/api/config/v1alpha1/zz_generated.deepcopy.go index 64ca7be7086..790d742904f 100644 --- a/api/config/v1alpha1/zz_generated.deepcopy.go +++ b/api/config/v1alpha1/zz_generated.deepcopy.go @@ -471,6 +471,18 @@ func (in *KubernetesPodSpec) DeepCopyInto(out *KubernetesPodSpec) { *out = new(v1.PodSecurityContext) (*in).DeepCopyInto(*out) } + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + *out = new(v1.Affinity) + (*in).DeepCopyInto(*out) + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesPodSpec. diff --git a/charts/gateway-helm/crds/generated/config.gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/crds/generated/config.gateway.envoyproxy.io_envoyproxies.yaml index 62071c56d24..2208100735f 100644 --- a/charts/gateway-helm/crds/generated/config.gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/crds/generated/config.gateway.envoyproxy.io_envoyproxies.yaml @@ -354,6 +354,1041 @@ spec: description: Pod defines the desired annotations and securityContext of container. properties: + affinity: + description: If specified, the pod's scheduling constraints. + properties: + nodeAffinity: + description: Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to + schedule pods to nodes that satisfy the + affinity expressions specified by this field, + but it may choose a node that violates one + or more of the expressions. The node that + is most preferred is the one with the greatest + sum of weights, i.e. for each node that + meets all of the scheduling requirements + (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum + by iterating through the elements of this + field and adding "weight" to the sum if + the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the + most preferred. + items: + description: An empty preferred scheduling + term matches all objects with implicit + weight 0 (i.e. it's a no-op). A null preferred + scheduling term matches no objects (i.e. + is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector + requirements by node's labels. + items: + description: A node selector requirement + is a selector that contains + values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key + that the selector applies + to. + type: string + operator: + description: Represents a + key's relationship to a + set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string + values. If the operator + is In or NotIn, the values + array must be non-empty. + If the operator is Exists + or DoesNotExist, the values + array must be empty. If + the operator is Gt or Lt, + the values array must have + a single element, which + will be interpreted as an + integer. This array is replaced + during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector + requirements by node's fields. + items: + description: A node selector requirement + is a selector that contains + values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key + that the selector applies + to. + type: string + operator: + description: Represents a + key's relationship to a + set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string + values. If the operator + is In or NotIn, the values + array must be non-empty. + If the operator is Exists + or DoesNotExist, the values + array must be empty. If + the operator is Gt or Lt, + the values array must have + a single element, which + will be interpreted as an + integer. This array is replaced + during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements + specified by this field are not met at scheduling + time, the pod will not be scheduled onto + the node. If the affinity requirements specified + by this field cease to be met at some point + during pod execution (e.g. due to an update), + the system may or may not try to eventually + evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node + selector terms. The terms are ORed. + items: + description: A null or empty node selector + term matches no objects. The requirements + of them are ANDed. The TopologySelectorTerm + type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector + requirements by node's labels. + items: + description: A node selector requirement + is a selector that contains + values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key + that the selector applies + to. + type: string + operator: + description: Represents a + key's relationship to a + set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string + values. If the operator + is In or NotIn, the values + array must be non-empty. + If the operator is Exists + or DoesNotExist, the values + array must be empty. If + the operator is Gt or Lt, + the values array must have + a single element, which + will be interpreted as an + integer. This array is replaced + during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector + requirements by node's fields. + items: + description: A node selector requirement + is a selector that contains + values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key + that the selector applies + to. + type: string + operator: + description: Represents a + key's relationship to a + set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: An array of string + values. If the operator + is In or NotIn, the values + array must be non-empty. + If the operator is Exists + or DoesNotExist, the values + array must be empty. If + the operator is Gt or Lt, + the values array must have + a single element, which + will be interpreted as an + integer. This array is replaced + during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to + schedule pods to nodes that satisfy the + affinity expressions specified by this field, + but it may choose a node that violates one + or more of the expressions. The node that + is most preferred is the one with the greatest + sum of weights, i.e. for each node that + meets all of the scheduling requirements + (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum + by iterating through the elements of this + field and adding "weight" to the sum if + the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest + sum are the most preferred. + items: + description: The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: A label query over + a set of resources, in this case + pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: A label selector + requirement is a selector + that contains values, a + key, and an operator that + relates the key and values. + properties: + key: + description: key is the + label key that the selector + applies to. + type: string + operator: + description: operator + represents a key's relationship + to a set of values. + Valid operators are + In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is + an array of string values. + If the operator is In + or NotIn, the values + array must be non-empty. + If the operator is Exists + or DoesNotExist, the + values array must be + empty. This array is + replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is + a map of {key,value} pairs. + A single {key,value} in the + matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", + the operator is "In", and + the values array contains + only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over + the set of namespaces that the + term applies to. The term is applied + to the union of the namespaces + selected by this field and the + ones listed in the namespaces + field. null selector and null + or empty namespaces list means + "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: A label selector + requirement is a selector + that contains values, a + key, and an operator that + relates the key and values. + properties: + key: + description: key is the + label key that the selector + applies to. + type: string + operator: + description: operator + represents a key's relationship + to a set of values. + Valid operators are + In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is + an array of string values. + If the operator is In + or NotIn, the values + array must be non-empty. + If the operator is Exists + or DoesNotExist, the + values array must be + empty. This array is + replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is + a map of {key,value} pairs. + A single {key,value} in the + matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", + the operator is "In", and + the values array contains + only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies + a static list of namespace names + that the term applies to. The + term is applied to the union of + the namespaces listed in this + field and the ones selected by + namespaceSelector. null or empty + namespaces list and null namespaceSelector + means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be + co-located (affinity) or not co-located + (anti-affinity) with the pods + matching the labelSelector in + the specified namespaces, where + co-located is defined as running + on a node whose value of the label + with key topologyKey matches that + of any node on which any of the + selected pods is running. Empty + topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with + matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements + specified by this field are not met at scheduling + time, the pod will not be scheduled onto + the node. If the affinity requirements specified + by this field cease to be met at some point + during pod execution (e.g. due to a pod + label update), the system may or may not + try to eventually evict the pod from its + node. When there are multiple elements, + the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all + terms must be satisfied. + items: + description: Defines a set of pods (namely + those matching the labelSelector relative + to the given namespace(s)) that this pod + should be co-located (affinity) or not + co-located (anti-affinity) with, where + co-located is defined as running on a + node whose value of the label with key + matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set + of resources, in this case pods. + properties: + matchExpressions: + description: matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector + requirement is a selector that + contains values, a key, and + an operator that relates the + key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to + a set of values. Valid operators + are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an + array of string values. + If the operator is In or + NotIn, the values array + must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be + empty. This array is replaced + during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map + of {key,value} pairs. A single + {key,value} in the matchLabels + map is equivalent to an element + of matchExpressions, whose key + field is "key", the operator is + "In", and the values array contains + only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the + set of namespaces that the term applies + to. The term is applied to the union + of the namespaces selected by this + field and the ones listed in the namespaces + field. null selector and null or empty + namespaces list means "this pod's + namespace". An empty selector ({}) + matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector + requirement is a selector that + contains values, a key, and + an operator that relates the + key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to + a set of values. Valid operators + are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an + array of string values. + If the operator is In or + NotIn, the values array + must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be + empty. This array is replaced + during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map + of {key,value} pairs. A single + {key,value} in the matchLabels + map is equivalent to an element + of matchExpressions, whose key + field is "key", the operator is + "In", and the values array contains + only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a + static list of namespace names that + the term applies to. The term is applied + to the union of the namespaces listed + in this field and the ones selected + by namespaceSelector. null or empty + namespaces list and null namespaceSelector + means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where + co-located is defined as running on + a node whose value of the label with + key topologyKey matches that of any + node on which any of the selected + pods is running. Empty topologyKey + is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to + schedule pods to nodes that satisfy the + anti-affinity expressions specified by this + field, but it may choose a node that violates + one or more of the expressions. The node + that is most preferred is the one with the + greatest sum of weights, i.e. for each node + that meets all of the scheduling requirements + (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute + a sum by iterating through the elements + of this field and adding "weight" to the + sum if the node has pods which matches the + corresponding podAffinityTerm; the node(s) + with the highest sum are the most preferred. + items: + description: The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: A label query over + a set of resources, in this case + pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: A label selector + requirement is a selector + that contains values, a + key, and an operator that + relates the key and values. + properties: + key: + description: key is the + label key that the selector + applies to. + type: string + operator: + description: operator + represents a key's relationship + to a set of values. + Valid operators are + In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is + an array of string values. + If the operator is In + or NotIn, the values + array must be non-empty. + If the operator is Exists + or DoesNotExist, the + values array must be + empty. This array is + replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is + a map of {key,value} pairs. + A single {key,value} in the + matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", + the operator is "In", and + the values array contains + only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over + the set of namespaces that the + term applies to. The term is applied + to the union of the namespaces + selected by this field and the + ones listed in the namespaces + field. null selector and null + or empty namespaces list means + "this pod's namespace". An empty + selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: A label selector + requirement is a selector + that contains values, a + key, and an operator that + relates the key and values. + properties: + key: + description: key is the + label key that the selector + applies to. + type: string + operator: + description: operator + represents a key's relationship + to a set of values. + Valid operators are + In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is + an array of string values. + If the operator is In + or NotIn, the values + array must be non-empty. + If the operator is Exists + or DoesNotExist, the + values array must be + empty. This array is + replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is + a map of {key,value} pairs. + A single {key,value} in the + matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", + the operator is "In", and + the values array contains + only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies + a static list of namespace names + that the term applies to. The + term is applied to the union of + the namespaces listed in this + field and the ones selected by + namespaceSelector. null or empty + namespaces list and null namespaceSelector + means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be + co-located (affinity) or not co-located + (anti-affinity) with the pods + matching the labelSelector in + the specified namespaces, where + co-located is defined as running + on a node whose value of the label + with key topologyKey matches that + of any node on which any of the + selected pods is running. Empty + topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with + matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements + specified by this field are not met at scheduling + time, the pod will not be scheduled onto + the node. If the anti-affinity requirements + specified by this field cease to be met + at some point during pod execution (e.g. + due to a pod label update), the system may + or may not try to eventually evict the pod + from its node. When there are multiple elements, + the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all + terms must be satisfied. + items: + description: Defines a set of pods (namely + those matching the labelSelector relative + to the given namespace(s)) that this pod + should be co-located (affinity) or not + co-located (anti-affinity) with, where + co-located is defined as running on a + node whose value of the label with key + matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set + of resources, in this case pods. + properties: + matchExpressions: + description: matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector + requirement is a selector that + contains values, a key, and + an operator that relates the + key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to + a set of values. Valid operators + are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an + array of string values. + If the operator is In or + NotIn, the values array + must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be + empty. This array is replaced + during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map + of {key,value} pairs. A single + {key,value} in the matchLabels + map is equivalent to an element + of matchExpressions, whose key + field is "key", the operator is + "In", and the values array contains + only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the + set of namespaces that the term applies + to. The term is applied to the union + of the namespaces selected by this + field and the ones listed in the namespaces + field. null selector and null or empty + namespaces list means "this pod's + namespace". An empty selector ({}) + matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector + requirement is a selector that + contains values, a key, and + an operator that relates the + key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to + a set of values. Valid operators + are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an + array of string values. + If the operator is In or + NotIn, the values array + must be non-empty. If the + operator is Exists or DoesNotExist, + the values array must be + empty. This array is replaced + during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map + of {key,value} pairs. A single + {key,value} in the matchLabels + map is equivalent to an element + of matchExpressions, whose key + field is "key", the operator is + "In", and the values array contains + only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a + static list of namespace names that + the term applies to. The term is applied + to the union of the namespaces listed + in this field and the ones selected + by namespaceSelector. null or empty + namespaces list and null namespaceSelector + means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where + co-located is defined as running on + a node whose value of the label with + key topologyKey matches that of any + node on which any of the selected + pods is running. Empty topologyKey + is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object annotations: additionalProperties: type: string @@ -563,6 +1598,53 @@ spec: type: string type: object type: object + tolerations: + description: If specified, the pod's tolerations. + items: + description: The pod this Toleration is attached + to tolerates any taint that matches the triple + using the matching operator + . + properties: + effect: + description: Effect indicates the taint effect + to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, + PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration + applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; + this combination means to match all values + and all keys. + type: string + operator: + description: Operator represents a key's relationship + to the value. Valid operators are Exists and + Equal. Defaults to Equal. Exists is equivalent + to wildcard for value, so that a pod can tolerate + all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the + period of time the toleration (which must + be of effect NoExecute, otherwise this field + is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint + forever (do not evict). Zero and negative + values will be treated as 0 (evict immediately) + by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration + matches to. If the operator is Exists, the + value should be empty, otherwise just a regular + string. + type: string + type: object + type: array type: object replicas: description: Replicas is the number of desired pods. Defaults diff --git a/docs/latest/api/config_types.md b/docs/latest/api/config_types.md index 87118b7dbe4..2e844ab976f 100644 --- a/docs/latest/api/config_types.md +++ b/docs/latest/api/config_types.md @@ -290,6 +290,8 @@ _Appears in:_ | --- | --- | | `annotations` _object (keys:string, values:string)_ | Annotations are the annotations that should be appended to the pods. By default, no pod annotations are appended. | | `securityContext` _[PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#podsecuritycontext-v1-core)_ | SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field. | +| `affinity` _[Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#affinity-v1-core)_ | If specified, the pod's scheduling constraints. | +| `tolerations` _[Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#toleration-v1-core) array_ | If specified, the pod's tolerations. | ## KubernetesServiceSpec diff --git a/internal/gatewayapi/testdata/envoyproxy-valid.in.yaml b/internal/gatewayapi/testdata/envoyproxy-valid.in.yaml index 364c4e11b16..9211dc02ac6 100644 --- a/internal/gatewayapi/testdata/envoyproxy-valid.in.yaml +++ b/internal/gatewayapi/testdata/envoyproxy-valid.in.yaml @@ -25,6 +25,20 @@ envoyproxy: annotations: key1: val1 key2: val2 + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: cloud.google.com/gke-nodepool + operator: In + values: + - router-node + tolerations: + - effect: NoSchedule + key: node-type + operator: Exists + value: "router" securityContext: runAsUser: 1000 runAsGroup: 3000 diff --git a/internal/gatewayapi/testdata/envoyproxy-valid.out.yaml b/internal/gatewayapi/testdata/envoyproxy-valid.out.yaml index cc684a1fdec..4bbe428dc79 100644 --- a/internal/gatewayapi/testdata/envoyproxy-valid.out.yaml +++ b/internal/gatewayapi/testdata/envoyproxy-valid.out.yaml @@ -74,6 +74,20 @@ infraIR: annotations: key1: val1 key2: val2 + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: cloud.google.com/gke-nodepool + operator: In + values: + - router-node + tolerations: + - effect: NoSchedule + key: node-type + operator: Exists + value: "router" securityContext: runAsUser: 1000 runAsGroup: 3000 diff --git a/internal/infrastructure/kubernetes/proxy_deployment.go b/internal/infrastructure/kubernetes/proxy_deployment.go index 97aa98f7d4f..1628307a017 100644 --- a/internal/infrastructure/kubernetes/proxy_deployment.go +++ b/internal/infrastructure/kubernetes/proxy_deployment.go @@ -89,6 +89,8 @@ func (i *Infra) expectedProxyDeployment(infra *ir.Infra) (*appsv1.Deployment, er RestartPolicy: corev1.RestartPolicyAlways, SchedulerName: "default-scheduler", SecurityContext: deploymentConfig.Pod.SecurityContext, + Affinity: deploymentConfig.Pod.Affinity, + Tolerations: deploymentConfig.Pod.Tolerations, Volumes: []corev1.Volume{ { Name: "certs", diff --git a/internal/infrastructure/kubernetes/ratelimit_deployment.go b/internal/infrastructure/kubernetes/ratelimit_deployment.go index 3bf2ea1d543..41e04d0535c 100644 --- a/internal/infrastructure/kubernetes/ratelimit_deployment.go +++ b/internal/infrastructure/kubernetes/ratelimit_deployment.go @@ -82,6 +82,8 @@ func (i *Infra) expectedRateLimitDeployment(infra *ir.RateLimitInfra) *appsv1.De RestartPolicy: corev1.RestartPolicyAlways, SchedulerName: "default-scheduler", SecurityContext: rateLimitDeployment.Pod.SecurityContext, + Affinity: rateLimitDeployment.Pod.Affinity, + Tolerations: rateLimitDeployment.Pod.Tolerations, Volumes: []corev1.Volume{ { Name: rateLimitInfraName, diff --git a/tools/make/teleport.mk b/tools/make/teleport.mk index 651747ac8d5..35f10f51a2e 100644 --- a/tools/make/teleport.mk +++ b/tools/make/teleport.mk @@ -38,8 +38,6 @@ teleport-push: teleport-build push .PHONY: teleport-build-multiarch teleport-build-multiarch: go.build.multiarch image-multiarch -teleport-helm-%: RELEASE_VERSION - .PHONY: teleport-helm-package teleport-helm-package: ## Package envoy gateway helm chart with teleprot specific overrides. teleport-helm-package: helm-package From 6b2b0256375c42392843506c99b2e5ef8fef5279 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 10:20:01 -0600 Subject: [PATCH 14/47] build(deps): bump golang.org/x/text in /tools/src/controller-gen (#25) Bumps [golang.org/x/text](https://github.com/golang/text) from 0.3.7 to 0.3.8. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.3.7...v0.3.8) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/src/controller-gen/go.mod | 2 +- tools/src/controller-gen/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/src/controller-gen/go.mod b/tools/src/controller-gen/go.mod index 9eaf4167a05..2e492977210 100644 --- a/tools/src/controller-gen/go.mod +++ b/tools/src/controller-gen/go.mod @@ -21,7 +21,7 @@ require ( golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/text v0.3.8 // indirect golang.org/x/tools v0.1.12 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/tools/src/controller-gen/go.sum b/tools/src/controller-gen/go.sum index 68f327ed0b4..653cdc87722 100644 --- a/tools/src/controller-gen/go.sum +++ b/tools/src/controller-gen/go.sum @@ -76,8 +76,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9w golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= From 3313a07ff61f1112a9e8c68ff1a71e1d9e38269b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 10:20:46 -0600 Subject: [PATCH 15/47] build(deps): bump golang.org/x/net from 0.4.0 to 0.7.0 in /tools/src/buf (#24) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.4.0 to 0.7.0. - [Commits](https://github.com/golang/net/compare/v0.4.0...v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/src/buf/go.mod | 8 ++++---- tools/src/buf/go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tools/src/buf/go.mod b/tools/src/buf/go.mod index ffeab992f54..0023d1d5238 100644 --- a/tools/src/buf/go.mod +++ b/tools/src/buf/go.mod @@ -50,11 +50,11 @@ require ( go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/mod v0.7.0 // indirect - golang.org/x/net v0.4.0 // indirect + golang.org/x/net v0.7.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.3.0 // indirect - golang.org/x/term v0.3.0 // indirect - golang.org/x/text v0.5.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/tools v0.4.0 // indirect google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/tools/src/buf/go.sum b/tools/src/buf/go.sum index 6c447f2204b..f0335fa3ebb 100644 --- a/tools/src/buf/go.sum +++ b/tools/src/buf/go.sum @@ -179,8 +179,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -197,14 +197,14 @@ golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From b509ddbd4b9bc088970b0fc32c0bfb9665aa9f90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 10:29:54 -0600 Subject: [PATCH 16/47] build(deps): bump golang.org/x/sys in /tools/src/setup-envtest (#23) Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.0.0-20220209214540-3681064d5158 to 0.1.0. - [Commits](https://github.com/golang/sys/commits/v0.1.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/src/setup-envtest/go.mod | 2 +- tools/src/setup-envtest/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/src/setup-envtest/go.mod b/tools/src/setup-envtest/go.mod index 99b4fd7a633..a42eba808bf 100644 --- a/tools/src/setup-envtest/go.mod +++ b/tools/src/setup-envtest/go.mod @@ -16,7 +16,7 @@ require ( go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.1 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect - golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect + golang.org/x/sys v0.1.0 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/protobuf v1.27.1 // indirect ) diff --git a/tools/src/setup-envtest/go.sum b/tools/src/setup-envtest/go.sum index dccbcd32e80..3f45550eb98 100644 --- a/tools/src/setup-envtest/go.sum +++ b/tools/src/setup-envtest/go.sum @@ -115,8 +115,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From b2b2d3899c6ff73cd508af77d3f5135c794a713b Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Tue, 1 Aug 2023 10:28:36 -0600 Subject: [PATCH 17/47] Create initial codeql workflow configuration Signed-off-by: Mike Jensen --- .github/workflows/codeql.yml | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000000..352417da3d1 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,41 @@ +name: "CodeQL" + +on: + push: + branches: [ "teleport" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "teleport" ] + +jobs: + analyze: + name: Analyze + runs-on: 'ubuntu-latest' + timeout-minutes: 240 + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go', 'python' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" From ab2e6aa3285639c149f919a3b8a280243c5d35c0 Mon Sep 17 00:00:00 2001 From: David Boslee Date: Thu, 3 Aug 2023 12:26:24 -0600 Subject: [PATCH 18/47] Add support for downstream proxy protocol (#28) --- internal/gatewayapi/route.go | 20 +++- ...tls-passthrough-and-proxy-protocol.in.yaml | 33 +++++++ ...ls-passthrough-and-proxy-protocol.out.yaml | 95 +++++++++++++++++++ internal/infrastructure/kubernetes/service.go | 1 + internal/ir/xds.go | 7 ++ internal/ir/zz_generated.deepcopy.go | 1 + .../tls-route-with-downstream-config.yaml | 13 +++ ...route-with-downstream-config.clusters.yaml | 11 +++ ...oute-with-downstream-config.endpoints.yaml | 10 ++ ...oute-with-downstream-config.listeners.yaml | 36 +++++++ ...s-route-with-downstream-config.routes.yaml | 1 + internal/xds/translator/translator.go | 28 ++++++ internal/xds/translator/translator_test.go | 3 + 13 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-tls-passthrough-and-proxy-protocol.in.yaml create mode 100644 internal/gatewayapi/testdata/gateway-with-listener-with-tls-passthrough-and-proxy-protocol.out.yaml create mode 100644 internal/xds/translator/testdata/in/xds-ir/tls-route-with-downstream-config.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.endpoints.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.routes.yaml diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index d285fadfc21..fc9c9da0846 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -28,6 +28,10 @@ const ( AnnotationRouteUpstreamMaxConnections = "cloud.teleport.dev/upstream-max-connections" // AnnotationTLSRouteProtos specifies the ALPN protos matched by a TLSRoute. AnnotationTLSRouteProtos = "cloud.teleport.dev/protos" + // AnnotationGatewayDownstreamProxyProtocol enables proxy protocol for a gateways listeners. + // Currently this only applies to TCP listeners. The value is expected to be set to "true", + // case-insensitive, to enable proxy protocol. All other values will be ignored. + AnnotationGatewayDownstreamProxyProtocol = "cloud.teleport.dev/downstream-proxy-protocol" ) var _ RoutesTranslator = (*Translator)(nil) @@ -591,6 +595,17 @@ func getUpstreamConfig(route client.Object) ir.UpstreamConfig { } } +func getDownstreamConfig(gateway *v1beta1.Gateway) ir.DownstreamConfig { + var enableProxyProtocol bool + annotations := gateway.GetAnnotations() + if v := annotations[AnnotationGatewayDownstreamProxyProtocol]; strings.ToLower(v) == "true" { + enableProxyProtocol = true + } + return ir.DownstreamConfig{ + EnableProxyProtocol: enableProxyProtocol, + } +} + func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resources *Resources, xdsIR XdsIRMap) { for _, parentRef := range tlsRoute.parentRefs { @@ -672,8 +687,9 @@ func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resour SNIs: hosts, Protos: protos, }, - Destinations: routeDestinations, - UpstreamConfig: getUpstreamConfig(tlsRoute), + Destinations: routeDestinations, + UpstreamConfig: getUpstreamConfig(tlsRoute), + DownstreamConfig: getDownstreamConfig(listener.gateway), } gwXdsIR := xdsIR[irKey] diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-passthrough-and-proxy-protocol.in.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-passthrough-and-proxy-protocol.in.yaml new file mode 100644 index 00000000000..d925bdf1456 --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-passthrough-and-proxy-protocol.in.yaml @@ -0,0 +1,33 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + annotations: + cloud.teleport.dev/downstream-proxy-protocol: "true" + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls-passthrough + protocol: TLS + port: 443 + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-2 + port: 8080 diff --git a/internal/gatewayapi/testdata/gateway-with-listener-with-tls-passthrough-and-proxy-protocol.out.yaml b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-passthrough-and-proxy-protocol.out.yaml new file mode 100644 index 00000000000..e3344a3f37f --- /dev/null +++ b/internal/gatewayapi/testdata/gateway-with-listener-with-tls-passthrough-and-proxy-protocol.out.yaml @@ -0,0 +1,95 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + annotations: + cloud.teleport.dev/downstream-proxy-protocol: "true" + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: tls-passthrough + protocol: TLS + port: 443 + tls: + mode: Passthrough + allowedRoutes: + namespaces: + from: All + status: + listeners: + - name: tls-passthrough + supportedKinds: + - group: gateway.networking.k8s.io + kind: TLSRoute + attachedRoutes: 1 + conditions: + - type: Programmed + status: "True" + reason: Programmed + message: Sending translated listener configuration to the data plane + - type: Accepted + status: "True" + reason: Accepted + message: Listener has been successfully translated +tlsRoutes: + - apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: TLSRoute + metadata: + namespace: default + name: tlsroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - backendRefs: + - name: service-2 + port: 8080 + status: + parents: + - parentRef: + namespace: envoy-gateway + name: gateway-1 + controllerName: gateway.envoyproxy.io/gatewayclass-controller + conditions: + - type: Accepted + status: "True" + reason: Accepted + message: Route is accepted + - type: ResolvedRefs + status: "True" + reason: ResolvedRefs + message: Resolved all the Object references for the Route +xdsIR: + envoy-gateway-gateway-1: + tcp: + - listenerName: envoy-gateway-gateway-1-tls-passthrough + routeName: envoy-gateway-gateway-1-tls-passthrough-tlsroute-1 + address: 0.0.0.0 + port: 10443 + tls: + snis: + - "*" + destinations: + - host: 7.7.7.7 + port: 8080 + weight: 1 + downstreamConfig: + enableProxyProtocol: true +infraIR: + envoy-gateway-gateway-1: + proxy: + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway-gateway-1 + listeners: + - address: "" + ports: + - name: tls-passthrough + protocol: "TLS" + servicePort: 443 + containerPort: 10443 diff --git a/internal/infrastructure/kubernetes/service.go b/internal/infrastructure/kubernetes/service.go index 5a893d0b187..662a404cb0d 100644 --- a/internal/infrastructure/kubernetes/service.go +++ b/internal/infrastructure/kubernetes/service.go @@ -34,6 +34,7 @@ func (i *Infra) createOrUpdateService(ctx context.Context, svc *corev1.Service) } } else { // Update if current value is different. + svc.Spec.LoadBalancerClass = current.Spec.LoadBalancerClass if !reflect.DeepEqual(svc.Spec, current.Spec) { svc.ResourceVersion = current.ResourceVersion svc.UID = current.UID diff --git a/internal/ir/xds.go b/internal/ir/xds.go index c9e92e1ba74..477432dbee9 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -618,6 +618,13 @@ type TCPListener struct { Destinations []*RouteDestination // UpstreamConfig provides configuration for upstream connections. UpstreamConfig UpstreamConfig + // DownstreamConfig provides configuration for downstream connections. + DownstreamConfig DownstreamConfig +} + +type DownstreamConfig struct { + // EnableProxyProtocol enables proxy protocol v2 for connections to downstream endpoints. + EnableProxyProtocol bool } // UpstreamConfig holds the upstream cluster configuration. diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 3e5a8562036..bbd6f5e49af 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -642,6 +642,7 @@ func (in *TCPListener) DeepCopyInto(out *TCPListener) { } } out.UpstreamConfig = in.UpstreamConfig + out.DownstreamConfig = in.DownstreamConfig } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPListener. diff --git a/internal/xds/translator/testdata/in/xds-ir/tls-route-with-downstream-config.yaml b/internal/xds/translator/testdata/in/xds-ir/tls-route-with-downstream-config.yaml new file mode 100644 index 00000000000..267caf58c5c --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/tls-route-with-downstream-config.yaml @@ -0,0 +1,13 @@ +tcp: +- listenerName: "tls-upstream-config-listener" + routeName: "tls-upstream-config-route" + address: "0.0.0.0" + port: 10080 + tls: + snis: + - foo.com + destinations: + - host: "1.2.3.4" + port: 50000 + downstreamConfig: + enableProxyProtocol: true diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.clusters.yaml new file mode 100644 index 00000000000..02da2d13736 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.clusters.yaml @@ -0,0 +1,11 @@ +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + name: tls-upstream-config-route + outlierDetection: {} + type: EDS diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.endpoints.yaml new file mode 100644 index 00000000000..903438d4f08 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.endpoints.yaml @@ -0,0 +1,10 @@ +- clusterName: tls-upstream-config-route + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + locality: {} diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.listeners.yaml new file mode 100644 index 00000000000..63896d7a53a --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.listeners.yaml @@ -0,0 +1,36 @@ +- accessLog: + - filter: + responseFlagFilter: + flags: + - NR + name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + filterChains: + - filterChainMatch: + serverNames: + - foo.com + filters: + - name: envoy.filters.network.tcp_proxy + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + accessLog: + - name: envoy.access_loggers.file + typedConfig: + '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout + cluster: tls-upstream-config-route + statPrefix: passthrough + listenerFilters: + - name: envoy.filters.listener.proxy_protocol + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.listener.proxy_protocol.v3.ProxyProtocol + - name: envoy.filters.listener.tls_inspector + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector + name: tls-upstream-config-listener diff --git a/internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.routes.yaml new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/tls-route-with-downstream-config.routes.yaml @@ -0,0 +1 @@ +[] diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index e5dd76e2ebd..be50100dcd7 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -13,9 +13,11 @@ import ( corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + filterproxyprotocol "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/listener/proxy_protocol/v3" proxyprotocolv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/proxy_protocol/v3" rawbufferv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/raw_buffer/v3" resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" + "github.com/envoyproxy/go-control-plane/pkg/wellknown" "github.com/tetratelabs/multierror" "google.golang.org/protobuf/types/known/anypb" @@ -240,6 +242,12 @@ func processTCPListenerXdsTranslation(tCtx *types.ResourceVersionTable, tcpListe tCtx.AddXdsResource(resourcev3.ListenerType, xdsListener) } + if tcpListener.DownstreamConfig.EnableProxyProtocol { + if err := addXdsProxyProtocolFilter(xdsListener); err != nil { + return err + } + } + if err := addXdsTCPFilterChain(xdsListener, tcpListener, tcpListener.RouteName); err != nil { return err } @@ -247,6 +255,26 @@ func processTCPListenerXdsTranslation(tCtx *types.ResourceVersionTable, tcpListe return nil } +func addXdsProxyProtocolFilter(xdsListener *listenerv3.Listener) error { + for _, filter := range xdsListener.ListenerFilters { + if filter.Name == wellknown.ProxyProtocol { + return nil + } + } + proxyProtocolConfig := &filterproxyprotocol.ProxyProtocol{} + proxyProtocolAny, err := anypb.New(proxyProtocolConfig) + if err != nil { + return err + } + xdsListener.ListenerFilters = append([]*listenerv3.ListenerFilter{{ + Name: wellknown.ProxyProtocol, + ConfigType: &listenerv3.ListenerFilter_TypedConfig{ + TypedConfig: proxyProtocolAny, + }, + }}, xdsListener.ListenerFilters...) + return nil +} + func buildXdsProxyProtocolTCPSocket() (*corev3.TransportSocket, error) { rawBuffer := &rawbufferv3.RawBuffer{} rawBufferAny, err := anypb.New(rawBuffer) diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go index 7757a4d2983..94583f22536 100644 --- a/internal/xds/translator/translator_test.go +++ b/internal/xds/translator/translator_test.go @@ -75,6 +75,9 @@ func TestTranslateXds(t *testing.T) { { name: "tls-route-passthrough", }, + { + name: "tls-route-with-downstream-config", + }, { name: "tls-route-with-upstream-config", }, From 43e0114d4df523b0de3165bfcb7f78888861a8da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Jul 2023 16:00:37 +0000 Subject: [PATCH 19/47] build(deps): bump go.mongodb.org/mongo-driver in /tools/src/kustomize Bumps [go.mongodb.org/mongo-driver](https://github.com/mongodb/mongo-go-driver) from 1.1.2 to 1.5.1. - [Release notes](https://github.com/mongodb/mongo-go-driver/releases) - [Commits](https://github.com/mongodb/mongo-go-driver/compare/v1.1.2...v1.5.1) --- updated-dependencies: - dependency-name: go.mongodb.org/mongo-driver dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/kustomize/go.mod | 32 +----- tools/src/kustomize/go.sum | 229 +++++++++++-------------------------- 2 files changed, 66 insertions(+), 195 deletions(-) diff --git a/tools/src/kustomize/go.mod b/tools/src/kustomize/go.mod index 91878b3174f..ba887f254a2 100644 --- a/tools/src/kustomize/go.mod +++ b/tools/src/kustomize/go.mod @@ -5,19 +5,11 @@ go 1.20 require sigs.k8s.io/kustomize/kustomize/v3 v3.10.0 require ( - cloud.google.com/go v0.38.0 // indirect - github.com/Azure/go-autorest/autorest v0.9.0 // indirect - github.com/Azure/go-autorest/autorest/adal v0.5.0 // indirect - github.com/Azure/go-autorest/autorest/date v0.1.0 // indirect - github.com/Azure/go-autorest/logger v0.1.0 // indirect - github.com/Azure/go-autorest/tracing v0.5.0 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect - github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 // indirect github.com/evanphx/json-patch v4.9.0+incompatible // indirect github.com/go-errors/errors v1.0.1 // indirect github.com/go-openapi/analysis v0.19.5 // indirect @@ -31,12 +23,7 @@ require ( github.com/go-openapi/swag v0.19.5 // indirect github.com/go-openapi/validate v0.19.8 // indirect github.com/go-stack/stack v1.8.0 // indirect - github.com/gogo/protobuf v1.3.1 // indirect - github.com/golang/protobuf v1.3.2 // indirect - github.com/google/gofuzz v1.1.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/googleapis/gnostic v0.1.0 // indirect - github.com/gophercloud/gophercloud v0.1.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.0 // indirect github.com/hashicorp/go-multierror v1.1.0 // indirect @@ -44,14 +31,11 @@ require ( github.com/hashicorp/go-version v1.1.0 // indirect github.com/imdario/mergo v0.3.5 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/json-iterator/go v1.1.8 // indirect github.com/mailru/easyjson v0.7.0 // indirect github.com/mattn/go-runewidth v0.0.7 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/mitchellh/mapstructure v1.1.2 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/olekukonko/tablewriter v0.0.4 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -63,28 +47,16 @@ require ( github.com/ulikunitz/xz v0.5.8 // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect github.com/yujunz/go-getter v1.5.1-lite.0.20201201013212-6d9c071adddf // indirect - go.mongodb.org/mongo-driver v1.1.2 // indirect + go.mongodb.org/mongo-driver v1.5.1 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect - golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect - golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect - golang.org/x/text v0.3.4 // indirect - golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect - google.golang.org/appengine v1.5.0 // indirect + golang.org/x/text v0.3.5 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect - k8s.io/api v0.18.10 // indirect - k8s.io/apimachinery v0.18.10 // indirect - k8s.io/client-go v0.18.10 // indirect - k8s.io/klog v1.0.0 // indirect - k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 // indirect - k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 // indirect sigs.k8s.io/kustomize/api v0.8.0 // indirect sigs.k8s.io/kustomize/cmd/config v0.9.1 // indirect sigs.k8s.io/kustomize/kyaml v0.10.9 // indirect - sigs.k8s.io/structured-merge-diff/v3 v3.0.0 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/tools/src/kustomize/go.sum b/tools/src/kustomize/go.sum index c8e33ba974a..e04e627d8e1 100644 --- a/tools/src/kustomize/go.sum +++ b/tools/src/kustomize/go.sum @@ -1,31 +1,12 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= -github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -38,6 +19,7 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= @@ -58,28 +40,19 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -90,7 +63,6 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -102,13 +74,11 @@ github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQH github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= @@ -124,7 +94,6 @@ github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6 github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= github.com/go-openapi/runtime v0.19.4 h1:csnOgcgAiuGoM/Po7PEpKDoNulCcF3FGbSnbHfxgjMI= github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= @@ -137,7 +106,6 @@ github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+Z github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM= github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= @@ -147,6 +115,7 @@ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.8 h1:YFzsdWIDfVuLvIOF+ZmKjVg1MbPJ1QgY9PihMwei1ys= github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= @@ -162,24 +131,42 @@ github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoM github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= @@ -195,34 +182,20 @@ github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlS github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI= -github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -236,30 +209,28 @@ github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhE github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= 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/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -271,14 +242,15 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -294,43 +266,29 @@ github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdI github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -346,6 +304,8 @@ github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d h1:K6eOUihrFLdZjZ github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -359,18 +319,19 @@ github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxr github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -380,7 +341,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -393,8 +353,6 @@ github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiff github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= -github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= @@ -405,20 +363,22 @@ github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yujunz/go-getter v1.4.1-lite h1:FhvNc94AXMZkfqUwfMKhnQEC9phkphSGdPTL7tIdhOM= -github.com/yujunz/go-getter v1.4.1-lite/go.mod h1:sbmqxXjyLunH1PkF3n7zSlnVeMvmYUuIl9ZVs/7NyCc= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yujunz/go-getter v1.5.1-lite.0.20201201013212-6d9c071adddf h1:gvEmqF83GB8R5XtrMseJb6A6R0OCtNAS8f4TmZg2dGc= github.com/yujunz/go-getter v1.5.1-lite.0.20201201013212-6d9c071adddf/go.mod h1:bL0Pr07HEdsMZ1WBqZIxXj96r5LnFsY4LgPaPEGkw1k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.mongodb.org/mongo-driver v1.5.1 h1:9nOVLGDfOaZ9R0tBumx/BcuqkbFpyTCU2r/Po7A2azI= +go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= @@ -427,34 +387,25 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -464,66 +415,57 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/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-20181011042414-1f849cf54d09/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-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/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-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -535,14 +477,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -552,12 +488,10 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -567,56 +501,21 @@ gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= -k8s.io/api v0.18.10 h1:M0/vqfuBAIIS7jsOOcosT0niiotZGqw6/zHTFpyi8iQ= -k8s.io/api v0.18.10/go.mod h1:xWtwPX1v47j5RTncmlMFGCx8b0avh+nP8OgZZ9hjo3M= -k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= -k8s.io/apimachinery v0.18.10 h1:Zupk3lPrUfhCF9puTpA8EvEfPsrhNZtrpOqdp66mKVs= -k8s.io/apimachinery v0.18.10/go.mod h1:PF5taHbXgTEJLU+xMypMmYTXTWPJ5LaW8bfsisxnEXk= -k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= -k8s.io/client-go v0.18.10 h1:fETWvjTtnE3/s+h0SYr2wvlKWFDF+NrhwAL/ddqVa2Q= -k8s.io/client-go v0.18.10/go.mod h1:XBkFAqPrzqfwmGkV5ac+mlgBpWcz5TkhLw2808q8C3c= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= -k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= -k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= -sigs.k8s.io/kustomize/api v0.6.5 h1:xaAWZamIhpt9Y5Kn/vuBcBhZH8/m0zwew1d4HepIgXg= -sigs.k8s.io/kustomize/api v0.6.5/go.mod h1:Z96Z48h3nOWgVAmd4JGABszi5znhEnz7xoWHy+Bl7L4= sigs.k8s.io/kustomize/api v0.8.0 h1:6C3GlBzDSrHg3q29k4nwAJHKyLAyZ+Fsz6DwZuao54w= sigs.k8s.io/kustomize/api v0.8.0/go.mod h1:Ih6Y6bOErR70EdapDtWitBzPG9HewyemRY6sFaQyugU= -sigs.k8s.io/kustomize/cmd/config v0.8.5 h1:e8uMA7LjAUss3cMefVoOY2tFr92brCTUAA6CmWgq57M= -sigs.k8s.io/kustomize/cmd/config v0.8.5/go.mod h1:PlZTWxL7Xi75mY64HBC9IVf2xwrEbzy3L7r7+19kON4= sigs.k8s.io/kustomize/cmd/config v0.9.1 h1:vU5aKij0ej0DroCAaiu8eObj7Z0HxNtbGi7wBmYYwP0= sigs.k8s.io/kustomize/cmd/config v0.9.1/go.mod h1:HAoxw2o0Kwfdre4+xAQpgD0ljMzUZvLnqcpItqnqFlk= -sigs.k8s.io/kustomize/kustomize/v3 v3.8.7 h1:9Yn76OTslYzNg4k2c7F279IgmpQW1C753SEJGh1tubw= -sigs.k8s.io/kustomize/kustomize/v3 v3.8.7/go.mod h1:EJBh54QBPZPSCJqfb9b+qGwzL9IE0Bp7hyM+wONC6Mw= sigs.k8s.io/kustomize/kustomize/v3 v3.10.0 h1:2ceyieTUl0Y5pRKAypYg1UkDSGV23cgxkeoBnz8obPI= sigs.k8s.io/kustomize/kustomize/v3 v3.10.0/go.mod h1:spVXMybMUUCzxzdS3TUxJU6s3kXPACyD1lK/cG+TOXI= -sigs.k8s.io/kustomize/kyaml v0.9.4 h1:DDuzZtjIzFqp2IPy4DTyCI69Cl3bDgcJODjI6sjF9NY= -sigs.k8s.io/kustomize/kyaml v0.9.4/go.mod h1:UTm64bSWVdBUA8EQoYCxVOaBQxUdIOr5LKWxA4GNbkw= sigs.k8s.io/kustomize/kyaml v0.10.9 h1:n3WNdvPPReRNDxW+XXd2JlyZ8EII721I21D1DBpBVBE= sigs.k8s.io/kustomize/kyaml v0.10.9/go.mod h1:K9yg1k/HB/6xNOf5VH3LhTo1DK9/5ykSZO5uIv+Y/1k= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= From c10ba2e8ecb94b14e74f63f74c689841265ccbf2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 16:30:42 +0000 Subject: [PATCH 20/47] build(deps): bump golang.org/x/sys in /tools/src/kind Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.0.0-20210630005230-0f9fa26af87c to 0.1.0. - [Commits](https://github.com/golang/sys/commits/v0.1.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/kind/go.mod | 2 +- tools/src/kind/go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/src/kind/go.mod b/tools/src/kind/go.mod index 96cab49fb7c..65ffa48d948 100644 --- a/tools/src/kind/go.mod +++ b/tools/src/kind/go.mod @@ -15,7 +15,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/spf13/cobra v1.4.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect + golang.org/x/sys v0.1.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/tools/src/kind/go.sum b/tools/src/kind/go.sum index e64a2d1425c..e1bec6c272d 100644 --- a/tools/src/kind/go.sum +++ b/tools/src/kind/go.sum @@ -29,8 +29,9 @@ github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 9b0fce3181e7c8f6b824c0ab71b4e55b5a912b91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Jul 2023 16:00:24 +0000 Subject: [PATCH 21/47] build(deps): bump github.com/docker/distribution in /tools/src/buf Bumps [github.com/docker/distribution](https://github.com/docker/distribution) from 2.8.1+incompatible to 2.8.2+incompatible. - [Release notes](https://github.com/docker/distribution/releases) - [Commits](https://github.com/docker/distribution/compare/v2.8.1...v2.8.2) --- updated-dependencies: - dependency-name: github.com/docker/distribution dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/buf/go.mod | 2 +- tools/src/buf/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/src/buf/go.mod b/tools/src/buf/go.mod index 0023d1d5238..d2aadd7c584 100644 --- a/tools/src/buf/go.mod +++ b/tools/src/buf/go.mod @@ -11,7 +11,7 @@ require ( github.com/bufbuild/protocompile v0.1.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/docker/cli v20.10.21+incompatible // indirect - github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/docker v20.10.21+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect diff --git a/tools/src/buf/go.sum b/tools/src/buf/go.sum index f0335fa3ebb..e2586199b20 100644 --- a/tools/src/buf/go.sum +++ b/tools/src/buf/go.sum @@ -27,8 +27,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/cli v20.10.21+incompatible h1:qVkgyYUnOLQ98LtXBrwd/duVqPT2X4SHndOuGsfwyhU= github.com/docker/cli v20.10.21+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= -github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.21+incompatible h1:UTLdBmHk3bEY+w8qeO5KttOhy6OmXWsl/FEet9Uswog= github.com/docker/docker v20.10.21+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= From d2d9ae2ad08752a7824a87d676ed32d445198638 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 19:36:02 +0000 Subject: [PATCH 22/47] build(deps): bump golang.org/x/net in /tools/src/crd-ref-docs Bumps [golang.org/x/net](https://github.com/golang/net) from 0.5.0 to 0.7.0. - [Commits](https://github.com/golang/net/compare/v0.5.0...v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/crd-ref-docs/go.mod | 12 +++------- tools/src/crd-ref-docs/go.sum | 44 ++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/tools/src/crd-ref-docs/go.mod b/tools/src/crd-ref-docs/go.mod index 0a88363dd62..4d79a20a90b 100644 --- a/tools/src/crd-ref-docs/go.mod +++ b/tools/src/crd-ref-docs/go.mod @@ -5,7 +5,6 @@ go 1.20 require github.com/elastic/crd-ref-docs v0.0.8 require ( - github.com/BurntSushi/toml v1.2.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible // indirect @@ -30,26 +29,21 @@ require ( github.com/spf13/pflag v1.0.5 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect - go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.5.0 // indirect - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/mod v0.7.0 // indirect - golang.org/x/net v0.5.0 // indirect - golang.org/x/sys v0.4.0 // indirect - golang.org/x/text v0.6.0 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/tools v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - honnef.co/go/tools v0.4.0 // indirect k8s.io/apiextensions-apiserver v0.26.1 // indirect k8s.io/apimachinery v0.26.1 // indirect - k8s.io/klog v1.0.0 // indirect k8s.io/klog/v2 v2.90.0 // indirect k8s.io/utils v0.0.0-20230202215443-34013725500c // indirect sigs.k8s.io/controller-tools v0.11.3 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/tools/src/crd-ref-docs/go.sum b/tools/src/crd-ref-docs/go.sum index 6e8ed62c6c2..9ee664149f7 100644 --- a/tools/src/crd-ref-docs/go.sum +++ b/tools/src/crd-ref-docs/go.sum @@ -1,25 +1,29 @@ -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elastic/crd-ref-docs v0.0.8 h1:Sy0A24E+L5aCj/8BGTgxkumCSNeckg4650d96o7QHiU= github.com/elastic/crd-ref-docs v0.0.8/go.mod h1:Jd1XDGgrvHzd/+qCf4SwBnOnLl4RaFRjind0ORnHovo= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/gobuffalo/flect v1.0.0 h1:eBFmskjXZgAOagiTXJH25Nt5sdFwNRcb8DKZsIsAUQI= github.com/gobuffalo/flect v1.0.0/go.mod h1:l9V6xSb4BlXwsxEMj3FVEub2nkdQjWhPvD8XTTlHPQc= @@ -27,6 +31,7 @@ github.com/goccy/go-yaml v1.9.8 h1:5gMyLUeU1/6zl+WFfR1hN7D2kf+1/eRGa7DFtToiBvQ= github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -43,6 +48,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -60,6 +67,12 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= @@ -73,14 +86,15 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -88,8 +102,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= @@ -98,11 +110,12 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -110,16 +123,15 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4= @@ -131,22 +143,22 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.4.0/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.90.0 h1:VkTxIV/FjRXn1fgNNcKGM8cfmL1Z33ZjXRTVxKCoF5M= k8s.io/klog/v2 v2.90.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/utils v0.0.0-20230202215443-34013725500c h1:YVqDar2X7YiQa/DVAXFMDIfGF8uGrHQemlrwRU5NlVI= @@ -157,4 +169,4 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= From 6c1fca69bf882d4db360fe5aa2ca4c9d6515ac16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 19:36:17 +0000 Subject: [PATCH 23/47] build(deps): bump golang.org/x/net in /tools/src/controller-gen Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20220722155237-a158d28d115b to 0.7.0. - [Commits](https://github.com/golang/net/commits/v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/controller-gen/go.mod | 6 +++--- tools/src/controller-gen/go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/src/controller-gen/go.mod b/tools/src/controller-gen/go.mod index 2e492977210..19baf65c112 100644 --- a/tools/src/controller-gen/go.mod +++ b/tools/src/controller-gen/go.mod @@ -19,9 +19,9 @@ require ( github.com/spf13/cobra v1.4.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect - golang.org/x/text v0.3.8 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/tools v0.1.12 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/tools/src/controller-gen/go.sum b/tools/src/controller-gen/go.sum index 653cdc87722..95cbbe1fdf4 100644 --- a/tools/src/controller-gen/go.sum +++ b/tools/src/controller-gen/go.sum @@ -62,8 +62,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -72,12 +72,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= From 0223bf7dfdda7347f15903650a8b79576759fabd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 19:36:39 +0000 Subject: [PATCH 24/47] build(deps): bump golang.org/x/text in /tools/src/kustomize Bumps [golang.org/x/text](https://github.com/golang/text) from 0.3.4 to 0.3.8. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.3.4...v0.3.8) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/kustomize/go.mod | 2 +- tools/src/kustomize/go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/src/kustomize/go.mod b/tools/src/kustomize/go.mod index ba887f254a2..c8a09c9601e 100644 --- a/tools/src/kustomize/go.mod +++ b/tools/src/kustomize/go.mod @@ -50,7 +50,7 @@ require ( go.mongodb.org/mongo-driver v1.5.1 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect - golang.org/x/text v0.3.5 // indirect + golang.org/x/text v0.3.8 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect diff --git a/tools/src/kustomize/go.sum b/tools/src/kustomize/go.sum index e04e627d8e1..4ac8742798b 100644 --- a/tools/src/kustomize/go.sum +++ b/tools/src/kustomize/go.sum @@ -448,8 +448,9 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From bf69c746aa6b8db513dce2a313aeec923d62516b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 19:36:53 +0000 Subject: [PATCH 25/47] build(deps): bump golang.org/x/text in /tools/src/setup-envtest Bumps [golang.org/x/text](https://github.com/golang/text) from 0.3.7 to 0.3.8. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.3.7...v0.3.8) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/setup-envtest/go.mod | 2 +- tools/src/setup-envtest/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/src/setup-envtest/go.mod b/tools/src/setup-envtest/go.mod index a42eba808bf..46969c4386b 100644 --- a/tools/src/setup-envtest/go.mod +++ b/tools/src/setup-envtest/go.mod @@ -17,6 +17,6 @@ require ( go.uber.org/zap v1.19.1 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/sys v0.1.0 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/text v0.3.8 // indirect google.golang.org/protobuf v1.27.1 // indirect ) diff --git a/tools/src/setup-envtest/go.sum b/tools/src/setup-envtest/go.sum index 3f45550eb98..94023d77745 100644 --- a/tools/src/setup-envtest/go.sum +++ b/tools/src/setup-envtest/go.sum @@ -121,8 +121,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= From 890f2cf6dcfcad66e7fb1a89dbd54498d292cc3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 19:37:29 +0000 Subject: [PATCH 26/47] build(deps): bump github.com/docker/docker in /tools/src/buf Bumps [github.com/docker/docker](https://github.com/docker/docker) from 20.10.21+incompatible to 20.10.24+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v20.10.21...v20.10.24) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/buf/go.mod | 2 +- tools/src/buf/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/src/buf/go.mod b/tools/src/buf/go.mod index d2aadd7c584..555092259f2 100644 --- a/tools/src/buf/go.mod +++ b/tools/src/buf/go.mod @@ -12,7 +12,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/docker/cli v20.10.21+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v20.10.21+incompatible // indirect + github.com/docker/docker v20.10.24+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect diff --git a/tools/src/buf/go.sum b/tools/src/buf/go.sum index e2586199b20..7cee33b7559 100644 --- a/tools/src/buf/go.sum +++ b/tools/src/buf/go.sum @@ -29,8 +29,8 @@ github.com/docker/cli v20.10.21+incompatible h1:qVkgyYUnOLQ98LtXBrwd/duVqPT2X4SH github.com/docker/cli v20.10.21+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.21+incompatible h1:UTLdBmHk3bEY+w8qeO5KttOhy6OmXWsl/FEet9Uswog= -github.com/docker/docker v20.10.21+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= +github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= From 193e6458a42c20b1f37b27cf2f83a89f1b8ccddd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 19:46:24 +0000 Subject: [PATCH 27/47] build(deps): bump golang.org/x/net in /tools/src/setup-envtest Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20220127200216-cd36cc0744dd to 0.7.0. - [Commits](https://github.com/golang/net/commits/v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/setup-envtest/go.mod | 6 +++--- tools/src/setup-envtest/go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/src/setup-envtest/go.mod b/tools/src/setup-envtest/go.mod index 46969c4386b..c2a5e9f6292 100644 --- a/tools/src/setup-envtest/go.mod +++ b/tools/src/setup-envtest/go.mod @@ -15,8 +15,8 @@ require ( go.uber.org/goleak v1.1.12 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.1 // indirect - golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/text v0.3.8 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect google.golang.org/protobuf v1.27.1 // indirect ) diff --git a/tools/src/setup-envtest/go.sum b/tools/src/setup-envtest/go.sum index 94023d77745..43ac49b1aef 100644 --- a/tools/src/setup-envtest/go.sum +++ b/tools/src/setup-envtest/go.sum @@ -93,8 +93,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -115,14 +115,14 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= From 409742f4243566d1faf3b90cefd9afd58ea602dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 19:47:00 +0000 Subject: [PATCH 28/47] build(deps): bump golang.org/x/net in /tools/src/kustomize Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20201110031124-69a78807bb2b to 0.7.0. - [Commits](https://github.com/golang/net/commits/v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/kustomize/go.mod | 4 ++-- tools/src/kustomize/go.sum | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/src/kustomize/go.mod b/tools/src/kustomize/go.mod index c8a09c9601e..f9fa5012238 100644 --- a/tools/src/kustomize/go.mod +++ b/tools/src/kustomize/go.mod @@ -49,8 +49,8 @@ require ( github.com/yujunz/go-getter v1.5.1-lite.0.20201201013212-6d9c071adddf // indirect go.mongodb.org/mongo-driver v1.5.1 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect - golang.org/x/text v0.3.8 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/text v0.7.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect diff --git a/tools/src/kustomize/go.sum b/tools/src/kustomize/go.sum index 4ac8742798b..0637717cd9c 100644 --- a/tools/src/kustomize/go.sum +++ b/tools/src/kustomize/go.sum @@ -416,8 +416,9 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -449,8 +450,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From c7da038efc0b7324bc4a5babb1fca1c623fee20d Mon Sep 17 00:00:00 2001 From: David Boslee Date: Wed, 16 Aug 2023 12:19:19 -0700 Subject: [PATCH 29/47] Implement equal for ir.Xds to reduce number of xds updates (#29) Previously any re-sync of a resource triggers a push to envoy even when nothing changes. This seems to be caused by re-ordering of the routes/listeners. This change sorts the listeners so that they are able to be checked for changes properly. --- internal/gatewayapi/runner/runner.go | 6 --- internal/ir/xds.go | 24 +++++++++++ internal/ir/xds_test.go | 60 ++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 6 deletions(-) diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index e555f7cbd26..3eed0b99de8 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -10,7 +10,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/gateway-api/apis/v1beta1" - "sigs.k8s.io/yaml" "github.com/envoyproxy/gateway/internal/envoygateway/config" extension "github.com/envoyproxy/gateway/internal/extension/types" @@ -74,11 +73,6 @@ func (r *Runner) subscribeAndTranslate(ctx context.Context) { // Translate to IR result := t.Translate(val) - yamlXdsIR, _ := yaml.Marshal(&result.XdsIR) - r.Logger.WithValues("output", "xds-ir").Info(string(yamlXdsIR)) - yamlInfraIR, _ := yaml.Marshal(&result.InfraIR) - r.Logger.WithValues("output", "infra-ir").Info(string(yamlInfraIR)) - var curKeys, newKeys []string // Get current IR keys for key := range r.InfraIR.LoadAll() { diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 477432dbee9..722ab8ee3d5 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -8,8 +8,10 @@ package ir import ( "errors" "net" + "reflect" "github.com/tetratelabs/multierror" + "golang.org/x/exp/slices" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -55,6 +57,28 @@ type Xds struct { UDP []*UDPListener } +// Equal implements the Comparable interface used by watchable.DeepEqual to skip unnecessary updates. +func (x1 *Xds) Equal(x2 *Xds) bool { + x1 = x1.DeepCopy() + x1.sort() + x2 = x2.DeepCopy() + x2.sort() + return reflect.DeepEqual(x1, x2) +} + +// sort ensures the listeners are in a consistent order. +func (x *Xds) sort() { + slices.SortFunc(x.HTTP, func(l1, l2 *HTTPListener) bool { + return l1.Name < l2.Name + }) + slices.SortFunc(x.TCP, func(l1, l2 *TCPListener) bool { + return l1.ListenerName+l1.RouteName < l2.ListenerName+l2.RouteName + }) + slices.SortFunc(x.UDP, func(l1, l2 *UDPListener) bool { + return l1.Name < l2.Name + }) +} + // Validate the fields within the Xds structure. func (x Xds) Validate() error { var errs error diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index e8d59b61245..ffc7d5226e0 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -8,6 +8,7 @@ package ir import ( "testing" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -662,6 +663,65 @@ func TestValidateTLSListenerConfig(t *testing.T) { } } +func TestEqualXds(t *testing.T) { + tests := []struct { + desc string + a *Xds + b *Xds + equal bool + }{ + { + desc: "out of order tcp listeners are equal", + a: &Xds{ + TCP: []*TCPListener{ + {ListenerName: "listener-1", RouteName: "route-1"}, + {ListenerName: "listener-2", RouteName: "route-2"}, + }, + }, + b: &Xds{ + TCP: []*TCPListener{ + {ListenerName: "listener-2", RouteName: "route-2"}, + {ListenerName: "listener-1", RouteName: "route-1"}, + }, + }, + equal: true, + }, + { + desc: "additional destination is not equal", + a: &Xds{ + TCP: []*TCPListener{ + { + ListenerName: "listener-1", + RouteName: "route-1", + Destinations: []*RouteDestination{ + {Host: "host-1"}, + }, + }, + }, + }, + b: &Xds{ + TCP: []*TCPListener{ + { + ListenerName: "listener-1", + RouteName: "route-1", + Destinations: []*RouteDestination{ + {Host: "host-1"}, + {Host: "host-2"}, + }, + }, + }, + }, + equal: false, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + require.Equal(t, tc.equal, cmp.Equal(tc.a, tc.b)) + }) + } +} + func TestValidateUDPListener(t *testing.T) { tests := []struct { name string From 37374eefda608427a954730d07444a2e1a989831 Mon Sep 17 00:00:00 2001 From: David Boslee Date: Mon, 21 Aug 2023 16:44:25 -0600 Subject: [PATCH 30/47] Compact resource reconciles into a single reconcile run (#30) --- internal/provider/kubernetes/controller.go | 36 +++++++++++++--------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index e7a7bb9e4d4..a9388d1a1b7 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -1054,7 +1054,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Only enqueue GatewayClass objects that match this Envoy Gateway's controller name. if err := c.Watch( &source.Kind{Type: &gwapiv1b1.GatewayClass{}}, - &handler.EnqueueRequestForObject{}, + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), predicate.NewPredicateFuncs(r.hasMatchingController), ); err != nil { return err @@ -1063,7 +1063,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Only enqueue EnvoyProxy objects that match this Envoy Gateway's GatewayClass. if err := c.Watch( &source.Kind{Type: &egcfgv1a1.EnvoyProxy{}}, - handler.EnqueueRequestsFromMapFunc(r.enqueueManagedClass), + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), predicate.ResourceVersionChangedPredicate{}, ); err != nil { return err @@ -1072,7 +1072,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Watch Gateway CRUDs and reconcile affected GatewayClass. if err := c.Watch( &source.Kind{Type: &gwapiv1b1.Gateway{}}, - &handler.EnqueueRequestForObject{}, + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), predicate.NewPredicateFuncs(r.validateGatewayForReconcile), ); err != nil { return err @@ -1084,7 +1084,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Watch HTTPRoute CRUDs and process affected Gateways. if err := c.Watch( &source.Kind{Type: &gwapiv1b1.HTTPRoute{}}, - &handler.EnqueueRequestForObject{}, + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), ); err != nil { return err } @@ -1095,7 +1095,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Watch GRPCRoute CRUDs and process affected Gateways. if err := c.Watch( &source.Kind{Type: &gwapiv1a2.GRPCRoute{}}, - &handler.EnqueueRequestForObject{}, + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), ); err != nil { return err } @@ -1106,7 +1106,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Watch TLSRoute CRUDs and process affected Gateways. if err := c.Watch( &source.Kind{Type: &gwapiv1a2.TLSRoute{}}, - &handler.EnqueueRequestForObject{}, + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), ); err != nil { return err } @@ -1117,7 +1117,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Watch UDPRoute CRUDs and process affected Gateways. if err := c.Watch( &source.Kind{Type: &gwapiv1a2.UDPRoute{}}, - &handler.EnqueueRequestForObject{}, + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), ); err != nil { return err } @@ -1128,7 +1128,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Watch TCPRoute CRUDs and process affected Gateways. if err := c.Watch( &source.Kind{Type: &gwapiv1a2.TCPRoute{}}, - &handler.EnqueueRequestForObject{}, + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), ); err != nil { return err } @@ -1139,7 +1139,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Watch Service CRUDs and process affected *Route objects. if err := c.Watch( &source.Kind{Type: &corev1.Service{}}, - &handler.EnqueueRequestForObject{}, + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), predicate.NewPredicateFuncs(r.validateServiceForReconcile)); err != nil { return err } @@ -1147,7 +1147,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Watch Secret CRUDs and process affected Gateways. if err := c.Watch( &source.Kind{Type: &corev1.Secret{}}, - &handler.EnqueueRequestForObject{}, + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), predicate.NewPredicateFuncs(r.validateSecretForReconcile), ); err != nil { return err @@ -1156,7 +1156,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Watch ReferenceGrant CRUDs and process affected Gateways. if err := c.Watch( &source.Kind{Type: &gwapiv1a2.ReferenceGrant{}}, - &handler.EnqueueRequestForObject{}, + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), ); err != nil { return err } @@ -1167,7 +1167,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Watch Deployment CRUDs and process affected Gateways. if err := c.Watch( &source.Kind{Type: &appsv1.Deployment{}}, - &handler.EnqueueRequestForObject{}, + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), predicate.NewPredicateFuncs(r.validateDeploymentForReconcile), ); err != nil { return err @@ -1176,7 +1176,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Watch AuthenticationFilter CRUDs and enqueue associated HTTPRoute objects. if err := c.Watch( &source.Kind{Type: &egv1a1.AuthenticationFilter{}}, - &handler.EnqueueRequestForObject{}, + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), predicate.NewPredicateFuncs(r.httpRoutesForAuthenticationFilter)); err != nil { return err } @@ -1184,7 +1184,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M // Watch RateLimitFilter CRUDs and enqueue associated HTTPRoute objects. if err := c.Watch( &source.Kind{Type: &egv1a1.RateLimitFilter{}}, - &handler.EnqueueRequestForObject{}, + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), predicate.NewPredicateFuncs(r.httpRoutesForRateLimitFilter)); err != nil { return err } @@ -1196,7 +1196,7 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M u := &unstructured.Unstructured{} u.SetGroupVersionKind(gvk) if err := c.Watch(&source.Kind{Type: u}, - &handler.EnqueueRequestForObject{}); err != nil { + handler.EnqueueRequestsFromMapFunc(r.enqueueClass)); err != nil { return err } r.log.Info("Watching additional resource", "resource", gvk.String()) @@ -1204,6 +1204,12 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M return nil } +func (r *gatewayAPIReconciler) enqueueClass(obj client.Object) []reconcile.Request { + return []reconcile.Request{{NamespacedName: types.NamespacedName{ + Name: string(r.classController), + }}} +} + func (r *gatewayAPIReconciler) enqueueManagedClass(obj client.Object) []reconcile.Request { ep, ok := obj.(*egcfgv1a1.EnvoyProxy) if !ok { From a063fdc96f7eaa22d62ea0e20505d1acc343355e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 21:30:30 +0000 Subject: [PATCH 31/47] build(deps): bump gopkg.in/yaml.v3 in /tools/src/kustomize Bumps gopkg.in/yaml.v3 from 3.0.0-20200313102051-9f266ea9e77c to 3.0.0. --- updated-dependencies: - dependency-name: gopkg.in/yaml.v3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/kustomize/go.mod | 2 +- tools/src/kustomize/go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/src/kustomize/go.mod b/tools/src/kustomize/go.mod index f9fa5012238..f693bb4e85c 100644 --- a/tools/src/kustomize/go.mod +++ b/tools/src/kustomize/go.mod @@ -53,7 +53,7 @@ require ( golang.org/x/text v0.7.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + gopkg.in/yaml.v3 v3.0.0 // indirect sigs.k8s.io/kustomize/api v0.8.0 // indirect sigs.k8s.io/kustomize/cmd/config v0.9.1 // indirect sigs.k8s.io/kustomize/kyaml v0.10.9 // indirect diff --git a/tools/src/kustomize/go.sum b/tools/src/kustomize/go.sum index 0637717cd9c..eafcf95e329 100644 --- a/tools/src/kustomize/go.sum +++ b/tools/src/kustomize/go.sum @@ -503,8 +503,9 @@ gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= From 1ad2c7b3021b584c7f2187633cddc135abb3620e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Oct 2023 19:39:02 +0000 Subject: [PATCH 32/47] build(deps): bump golang.org/x/net in /tools/src/kustomize Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.7.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/kustomize/go.mod | 4 ++-- tools/src/kustomize/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/src/kustomize/go.mod b/tools/src/kustomize/go.mod index f693bb4e85c..ec69473a136 100644 --- a/tools/src/kustomize/go.mod +++ b/tools/src/kustomize/go.mod @@ -49,8 +49,8 @@ require ( github.com/yujunz/go-getter v1.5.1-lite.0.20201201013212-6d9c071adddf // indirect go.mongodb.org/mongo-driver v1.5.1 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/text v0.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.0 // indirect diff --git a/tools/src/kustomize/go.sum b/tools/src/kustomize/go.sum index eafcf95e329..27c40e9e4e7 100644 --- a/tools/src/kustomize/go.sum +++ b/tools/src/kustomize/go.sum @@ -417,8 +417,8 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -450,8 +450,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 2a5a99d99058b8b39de644d92fad462dfb42d307 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Oct 2023 19:39:02 +0000 Subject: [PATCH 33/47] build(deps): bump golang.org/x/net in /tools/src/controller-gen Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.7.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/controller-gen/go.mod | 10 +++++----- tools/src/controller-gen/go.sum | 21 +++++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/tools/src/controller-gen/go.mod b/tools/src/controller-gen/go.mod index 19baf65c112..973f6c950bc 100644 --- a/tools/src/controller-gen/go.mod +++ b/tools/src/controller-gen/go.mod @@ -18,11 +18,11 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/spf13/cobra v1.4.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - golang.org/x/tools v0.1.12 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.6.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/tools/src/controller-gen/go.sum b/tools/src/controller-gen/go.sum index 95cbbe1fdf4..aceec953bd9 100644 --- a/tools/src/controller-gen/go.sum +++ b/tools/src/controller-gen/go.sum @@ -56,34 +56,35 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From cfadf44b6d2ebd8058e488a5fc7682a363b5e72e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Oct 2023 19:39:04 +0000 Subject: [PATCH 34/47] build(deps): bump golang.org/x/net in /tools/src/buf Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.7.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/buf/go.mod | 12 ++++++------ tools/src/buf/go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tools/src/buf/go.mod b/tools/src/buf/go.mod index 555092259f2..f23ae1fb99b 100644 --- a/tools/src/buf/go.mod +++ b/tools/src/buf/go.mod @@ -49,13 +49,13 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/mod v0.7.0 // indirect - golang.org/x/net v0.7.0 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - golang.org/x/tools v0.4.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.6.0 // indirect google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/tools/src/buf/go.sum b/tools/src/buf/go.sum index 7cee33b7559..2ea5458484d 100644 --- a/tools/src/buf/go.sum +++ b/tools/src/buf/go.sum @@ -168,8 +168,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -179,8 +179,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -197,14 +197,14 @@ golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -214,8 +214,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 8c15fa20685ba77e66091f4e66e0e647d7bada1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Oct 2023 19:39:06 +0000 Subject: [PATCH 35/47] build(deps): bump golang.org/x/net in /tools/src/setup-envtest Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.7.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/setup-envtest/go.mod | 6 +++--- tools/src/setup-envtest/go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/src/setup-envtest/go.mod b/tools/src/setup-envtest/go.mod index c2a5e9f6292..eaadec15750 100644 --- a/tools/src/setup-envtest/go.mod +++ b/tools/src/setup-envtest/go.mod @@ -15,8 +15,8 @@ require ( go.uber.org/goleak v1.1.12 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.1 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect google.golang.org/protobuf v1.27.1 // indirect ) diff --git a/tools/src/setup-envtest/go.sum b/tools/src/setup-envtest/go.sum index 43ac49b1aef..51c51d32862 100644 --- a/tools/src/setup-envtest/go.sum +++ b/tools/src/setup-envtest/go.sum @@ -93,8 +93,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -115,14 +115,14 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= From b6d7b0dc9cc1ce68a20010a28e47964d3d78c761 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Oct 2023 19:39:08 +0000 Subject: [PATCH 36/47] build(deps): bump golang.org/x/net from 0.8.0 to 0.17.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.8.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.8.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 24f9f398727..376086b2c34 100644 --- a/go.mod +++ b/go.mod @@ -91,11 +91,11 @@ require ( go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/net v0.8.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/term v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index c04eb6dd3b6..7024b836781 100644 --- a/go.sum +++ b/go.sum @@ -457,8 +457,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -524,12 +524,12 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -538,8 +538,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 273b78c3d9332f2f6a7c01fc14e94c1cdfb25f86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Oct 2023 19:39:16 +0000 Subject: [PATCH 37/47] build(deps): bump golang.org/x/net in /tools/src/crd-ref-docs Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.7.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/crd-ref-docs/go.mod | 12 ++++++------ tools/src/crd-ref-docs/go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tools/src/crd-ref-docs/go.mod b/tools/src/crd-ref-docs/go.mod index 4d79a20a90b..cb0331343c3 100644 --- a/tools/src/crd-ref-docs/go.mod +++ b/tools/src/crd-ref-docs/go.mod @@ -30,12 +30,12 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.5.0 // indirect - golang.org/x/mod v0.7.0 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect - golang.org/x/tools v0.5.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/tools/src/crd-ref-docs/go.sum b/tools/src/crd-ref-docs/go.sum index 9ee664149f7..b83014a40fe 100644 --- a/tools/src/crd-ref-docs/go.sum +++ b/tools/src/crd-ref-docs/go.sum @@ -100,18 +100,18 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -123,19 +123,19 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 02a15f04e62b967c1387971f59ee6bcf8a3c88dd Mon Sep 17 00:00:00 2001 From: David Boslee Date: Thu, 12 Oct 2023 14:34:27 -0600 Subject: [PATCH 38/47] Run cd action for teleport branch (#38) --- .github/workflows/cd.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index e263bdf72cd..9eb2362ce1c 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -3,6 +3,8 @@ on: push: tags: - 'v[0-9]+.[0-9]+.[0-9]+.*' + branches: + - teleport permissions: contents: read From 58da048f62d6793bd09762516691afc6c1bc776e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 10:08:21 -0600 Subject: [PATCH 39/47] build(deps): bump google.golang.org/grpc from 1.54.0 to 1.56.3 (#39) * build(deps): bump google.golang.org/grpc from 1.54.0 to 1.56.3 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.54.0 to 1.56.3. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.54.0...v1.56.3) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Run teleport-generate --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: David Boslee --- go.mod | 14 +++++----- go.sum | 32 ++++++++++------------- internal/xds/extensions/extensions.gen.go | 9 +++++++ 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 376086b2c34..22de513728e 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/envoyproxy/gateway go 1.20 require ( - github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b - github.com/envoyproxy/go-control-plane v0.11.1-0.20230320001644-5efe59dc39e4 + github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 + github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f github.com/envoyproxy/ratelimit v1.4.1-0.20230109191524-5f3f5a4cf573 github.com/go-logr/logr v1.2.4 github.com/go-logr/zapr v1.2.3 @@ -13,13 +13,13 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.3 github.com/telepresenceio/watchable v0.0.0-20220726211108-9bb86f92afa7 github.com/tetratelabs/multierror v1.1.1 github.com/tsaarni/certyaml v0.9.2 go.uber.org/zap v1.24.0 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e - google.golang.org/grpc v1.54.0 + google.golang.org/grpc v1.56.3 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.26.3 @@ -42,7 +42,7 @@ require ( github.com/chai2010/gettext-go v1.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/envoyproxy/protoc-gen-validate v0.9.1 // indirect + github.com/envoyproxy/protoc-gen-validate v0.10.1 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect @@ -92,14 +92,14 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.4.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiextensions-apiserver v0.26.1 // indirect diff --git a/go.sum b/go.sum index 7024b836781..1c12f521763 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b h1:ACGZRIr7HsgBKHsueQ1yM4WaVaXh21ynwqsF8M8tXhA= -github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= @@ -92,11 +92,11 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.11.1-0.20230320001644-5efe59dc39e4 h1:epeQERmRfa+7YuY3CLn+9/JdzZ+5+JxBSRyeFeY57Is= -github.com/envoyproxy/go-control-plane v0.11.1-0.20230320001644-5efe59dc39e4/go.mod h1:84cjSkVxFD9Pi/gvI5AOq5NPhGsmS8oPsJLtCON6eK8= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f h1:7T++XKzy4xg7PKy+bM+Sa9/oe1OC88yz2hXQUISoXfA= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.9.1 h1:PS7VIOgmSVhWUEeZwTe7z7zouA22Cr590PzXKbZHOVY= -github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/ratelimit v1.4.1-0.20230109191524-5f3f5a4cf573 h1:R0q3SnW2O9CnSHt9Mx6I/v3nz3HH3xzqeOQ+zSpszpA= github.com/envoyproxy/ratelimit v1.4.1-0.20230109191524-5f3f5a4cf573/go.mod h1:J780gPM5tWaDEY2n+A7q744I9Bot8OJuBHZbwCfDpVc= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= @@ -339,19 +339,15 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/telepresenceio/telepresence/rpc/v2 v2.6.8 h1:q5V85LBT9bA/c4YPa/kMvJGyKZDgBPJTftlAMqJx7j4= github.com/telepresenceio/watchable v0.0.0-20220726211108-9bb86f92afa7 h1:GMw3nEaOVyi+tNiGko5kAeRtoiEIpXNHmISyZ7fpw14= github.com/telepresenceio/watchable v0.0.0-20220726211108-9bb86f92afa7/go.mod h1:ihJ97e2gsd8GuzFF/I3B1qcik3XZLpXjumQifXi8Slg= @@ -467,8 +463,8 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -651,8 +647,8 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -669,8 +665,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/internal/xds/extensions/extensions.gen.go b/internal/xds/extensions/extensions.gen.go index 73eeae4df82..ac062e3f1c9 100644 --- a/internal/xds/extensions/extensions.gen.go +++ b/internal/xds/extensions/extensions.gen.go @@ -33,6 +33,7 @@ import ( _ "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/private_key_providers/cryptomb/v3alpha" _ "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/private_key_providers/qat/v3alpha" _ "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/regex_engines/hyperscan/v3alpha" + _ "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/router/cluster_specifier/golang/v3alpha" _ "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/vcl/v3alpha" _ "github.com/envoyproxy/go-control-plane/envoy/admin/v2alpha" _ "github.com/envoyproxy/go-control-plane/envoy/admin/v3" @@ -198,6 +199,7 @@ import ( _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/fault/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/file_system_buffer/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/gcp_authn/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/geoip/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/grpc_http1_bridge/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/grpc_http1_reverse_bridge/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/grpc_json_transcoder/v3" @@ -254,6 +256,7 @@ import ( _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/zookeeper_proxy/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/udp/dns_filter/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/udp/udp_proxy/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/formatter/cel/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/formatter/metadata/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/formatter/req_without_query/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/health_checkers/redis/v3" @@ -274,18 +277,22 @@ import ( _ "github.com/envoyproxy/go-control-plane/envoy/extensions/internal_redirect/safe_cross_scheme/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/key_value/file_based/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/client_side_weighted_round_robin/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/cluster_provided/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/common/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/least_request/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/maglev/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/pick_first/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/random/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/ring_hash/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/round_robin/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/subset/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/wrr_locality/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/common_inputs/environment_variable/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/common_inputs/network/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/common_inputs/ssl/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/input_matchers/consistent_hashing/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/input_matchers/ip/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/input_matchers/runtime_fraction/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/apple/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/cares/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3" @@ -297,6 +304,7 @@ import ( _ "github.com/envoyproxy/go-control-plane/envoy/extensions/quic/proof_source/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/quic/server_preferred_address/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/rate_limit_descriptors/expr/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/rbac/audit_loggers/stream/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/rbac/matchers/upstream_ip_port/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/regex_engines/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/request_id/uuid/v3" @@ -308,6 +316,7 @@ import ( _ "github.com/envoyproxy/go-control-plane/envoy/extensions/retry/host/previous_hosts/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/retry/priority/previous_priorities/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/stat_sinks/graphite_statsd/v3" + _ "github.com/envoyproxy/go-control-plane/envoy/extensions/stat_sinks/open_telemetry/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/stat_sinks/wasm/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/alts/v3" _ "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/http_11_proxy/v3" From 14e9af691913ab4e45fe5dc30ba81a152b5cfba6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:10:44 +0000 Subject: [PATCH 40/47] build(deps): bump github.com/docker/docker in /tools/src/buf Bumps [github.com/docker/docker](https://github.com/docker/docker) from 20.10.24+incompatible to 24.0.7+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v20.10.24...v24.0.7) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/buf/go.mod | 2 +- tools/src/buf/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/src/buf/go.mod b/tools/src/buf/go.mod index f23ae1fb99b..cb404683934 100644 --- a/tools/src/buf/go.mod +++ b/tools/src/buf/go.mod @@ -12,7 +12,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/docker/cli v20.10.21+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v20.10.24+incompatible // indirect + github.com/docker/docker v24.0.7+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect diff --git a/tools/src/buf/go.sum b/tools/src/buf/go.sum index 2ea5458484d..aa6fe404ed3 100644 --- a/tools/src/buf/go.sum +++ b/tools/src/buf/go.sum @@ -29,8 +29,8 @@ github.com/docker/cli v20.10.21+incompatible h1:qVkgyYUnOLQ98LtXBrwd/duVqPT2X4SH github.com/docker/cli v20.10.21+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= -github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= From 2018cbb795e9e34603868d5b573647860822ec03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 17:06:07 +0000 Subject: [PATCH 41/47] build(deps): bump golang.org/x/crypto in /tools/src/crd-ref-docs Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0. - [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/crd-ref-docs/go.mod | 6 +++--- tools/src/crd-ref-docs/go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/src/crd-ref-docs/go.mod b/tools/src/crd-ref-docs/go.mod index cb0331343c3..aeb6314ba39 100644 --- a/tools/src/crd-ref-docs/go.mod +++ b/tools/src/crd-ref-docs/go.mod @@ -30,11 +30,11 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.14.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/tools/src/crd-ref-docs/go.sum b/tools/src/crd-ref-docs/go.sum index b83014a40fe..a7ff586f935 100644 --- a/tools/src/crd-ref-docs/go.sum +++ b/tools/src/crd-ref-docs/go.sum @@ -100,8 +100,8 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= @@ -123,13 +123,13 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= From 88fab0151fffec0a2821bcf3da85341e5cbc2968 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 21:53:01 +0000 Subject: [PATCH 42/47] build(deps): bump google.golang.org/protobuf in /tools/src/golangci-lint Bumps google.golang.org/protobuf from 1.28.0 to 1.33.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/golangci-lint/go.mod | 2 +- tools/src/golangci-lint/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/src/golangci-lint/go.mod b/tools/src/golangci-lint/go.mod index b325b799103..45e0db909d4 100644 --- a/tools/src/golangci-lint/go.mod +++ b/tools/src/golangci-lint/go.mod @@ -166,7 +166,7 @@ require ( golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.7.0 // indirect golang.org/x/tools v0.7.0 // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/tools/src/golangci-lint/go.sum b/tools/src/golangci-lint/go.sum index 723b435a2b0..922b089fece 100644 --- a/tools/src/golangci-lint/go.sum +++ b/tools/src/golangci-lint/go.sum @@ -924,8 +924,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From e9cee2b9d8be16d67f568b8639c1a446eb3bc39c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 21:35:04 +0000 Subject: [PATCH 43/47] build(deps): bump google.golang.org/protobuf in /tools/src/setup-envtest Bumps google.golang.org/protobuf from 1.27.1 to 1.33.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/setup-envtest/go.mod | 2 +- tools/src/setup-envtest/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/src/setup-envtest/go.mod b/tools/src/setup-envtest/go.mod index eaadec15750..40f294cc879 100644 --- a/tools/src/setup-envtest/go.mod +++ b/tools/src/setup-envtest/go.mod @@ -18,5 +18,5 @@ require ( golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect - google.golang.org/protobuf v1.27.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/tools/src/setup-envtest/go.sum b/tools/src/setup-envtest/go.sum index 51c51d32862..7d8d4c046e0 100644 --- a/tools/src/setup-envtest/go.sum +++ b/tools/src/setup-envtest/go.sum @@ -141,8 +141,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= From be8b8a8a574a97dcf21937700f04c7daa81750fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:18:52 +0000 Subject: [PATCH 44/47] build(deps): bump google.golang.org/protobuf from 1.30.0 to 1.33.0 Bumps google.golang.org/protobuf from 1.30.0 to 1.33.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 22de513728e..90786eac18a 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( go.uber.org/zap v1.24.0 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e google.golang.org/grpc v1.56.3 - google.golang.org/protobuf v1.30.0 + google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.26.3 k8s.io/apimachinery v0.26.3 diff --git a/go.sum b/go.sum index 1c12f521763..23d57dc8671 100644 --- a/go.sum +++ b/go.sum @@ -680,8 +680,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 640a0e00443b4d0951ad6e2950a392362cc82d05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:18:43 +0000 Subject: [PATCH 45/47] build(deps): bump google.golang.org/protobuf in /tools/src/buf Bumps google.golang.org/protobuf from 1.28.2-0.20220831092852-f930b1dc76e8 to 1.33.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/buf/go.mod | 2 +- tools/src/buf/go.sum | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tools/src/buf/go.mod b/tools/src/buf/go.mod index cb404683934..b992c422a25 100644 --- a/tools/src/buf/go.mod +++ b/tools/src/buf/go.mod @@ -56,6 +56,6 @@ require ( golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.6.0 // indirect - google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/tools/src/buf/go.sum b/tools/src/buf/go.sum index aa6fe404ed3..4e5da2957e4 100644 --- a/tools/src/buf/go.sum +++ b/tools/src/buf/go.sum @@ -65,7 +65,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -73,7 +72,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-containerregistry v0.12.1 h1:W1mzdNUTx4Zla4JaixCRLhORcR7G6KxE5hHl5fkPsp8= github.com/google/go-containerregistry v0.12.1/go.mod h1:sdIK+oHQO7B93xI8UweYdl887YhuIwg9vz8BSLH3+8k= @@ -239,9 +237,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= -google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From e93e1436eaf4603d5a8a0beb3c7d309c785e729f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:18:29 +0000 Subject: [PATCH 46/47] build(deps): bump google.golang.org/protobuf Bumps google.golang.org/protobuf from 1.28.1 to 1.33.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/src/protoc-gen-go-grpc/go.mod | 2 +- tools/src/protoc-gen-go-grpc/go.sum | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/tools/src/protoc-gen-go-grpc/go.mod b/tools/src/protoc-gen-go-grpc/go.mod index 7d3884774c6..d98227f8099 100644 --- a/tools/src/protoc-gen-go-grpc/go.mod +++ b/tools/src/protoc-gen-go-grpc/go.mod @@ -4,4 +4,4 @@ go 1.20 require google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 -require google.golang.org/protobuf v1.28.1 // indirect +require google.golang.org/protobuf v1.33.0 // indirect diff --git a/tools/src/protoc-gen-go-grpc/go.sum b/tools/src/protoc-gen-go-grpc/go.sum index 8d750cf2b44..72751cb9913 100644 --- a/tools/src/protoc-gen-go-grpc/go.sum +++ b/tools/src/protoc-gen-go-grpc/go.sum @@ -1,10 +1,6 @@ -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= From db857e54999a33cf6e6a53996664bd8766cd0c2b Mon Sep 17 00:00:00 2001 From: David Boslee Date: Tue, 19 Mar 2024 12:55:03 -0600 Subject: [PATCH 47/47] Use static clusters instead of EDS It appears that when a cluster is updated without a following endpoint update a cluster can lose its endpoint configuration. I attempted to update endpoints on each cluster change to prevent this but it was still possible for the cluster update to happen after the endpoint update. This appears related to https://github.com/envoyproxy/envoy/issues/13009 By using static clusters the endpoints are included in the cluster update. In testing this appears to resolve the issue where endpoints disappear since we no longer rely on EDS. --- internal/xds/translator/cluster.go | 11 +---------- internal/xds/translator/translator.go | 7 +------ 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/internal/xds/translator/cluster.go b/internal/xds/translator/cluster.go index f3b20b1b12e..fa0d15c5fbe 100644 --- a/internal/xds/translator/cluster.go +++ b/internal/xds/translator/cluster.go @@ -12,7 +12,6 @@ import ( corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" endpointv3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" httpv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3" - "github.com/envoyproxy/go-control-plane/pkg/resource/v3" "github.com/golang/protobuf/ptypes/wrappers" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/durationpb" @@ -60,15 +59,7 @@ func buildXdsCluster(routeName string, tSocket *corev3.TransportSocket, protocol } if endpointType == Static { - cluster.ClusterDiscoveryType = &clusterv3.Cluster_Type{Type: clusterv3.Cluster_EDS} - cluster.EdsClusterConfig = &clusterv3.Cluster_EdsClusterConfig{ - EdsConfig: &corev3.ConfigSource{ - ResourceApiVersion: resource.DefaultAPIVersion, - ConfigSourceSpecifier: &corev3.ConfigSource_Ads{ - Ads: &corev3.AggregatedConfigSource{}, - }, - }, - } + cluster.ClusterDiscoveryType = &clusterv3.Cluster_Type{Type: clusterv3.Cluster_STATIC} } else { cluster.ClusterDiscoveryType = &clusterv3.Cluster_Type{Type: clusterv3.Cluster_STRICT_DNS} cluster.DnsRefreshRate = durationpb.New(30 * time.Second) diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index be50100dcd7..005f5214d38 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -369,12 +369,7 @@ func findXdsCluster(tCtx *types.ResourceVersionTable, name string) *clusterv3.Cl func addXdsCluster(tCtx *types.ResourceVersionTable, args addXdsClusterArgs) { xdsCluster := buildXdsCluster(args.name, args.tSocket, args.protocol, args.endpoint, args.circuitBreakers) xdsEndpoints := buildXdsClusterLoadAssignment(args.name, args.destinations) - // Use EDS for static endpoints - if args.endpoint == Static { - tCtx.AddXdsResource(resourcev3.EndpointType, xdsEndpoints) - } else { - xdsCluster.LoadAssignment = xdsEndpoints - } + xdsCluster.LoadAssignment = xdsEndpoints tCtx.AddXdsResource(resourcev3.ClusterType, xdsCluster) }